StackOverFlowError是常见的JVM错误之一。在这篇博文中,让我们学习线程堆栈的内部机制,可以触发StackOverFlowError的原因以及解决此错误的潜在解决方案。
为了更深入地了解StackOverFlowError,让我们回顾一下这个简单的程序:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public class SimpleExample {
public static void main(String args[]) {
a()
}
public static void a() {
int x = 0;
b();
}
public static void b() {
Car y = new Car();
c();
}
public static void c() {
float z = 0f;
System.out.println("Hello");
}
}
使用以下执行代码,此程序非常简单:
- 首先调用main()方法
- main()方法调用a()方法。在()方法内部,整数变量’x’被初始化为值0。
- a()方法又调用b()方法。在b()方法内部构造Car对象并将其赋值给变量’y’。
- b()方法依次调用c()方法。在c()方法中,float变量’z’被初始化为值0。
让我们看下执行上述简单程序时背后发生的事情。应用程序中的每个线程都有自己的堆栈。每个堆栈都有多个堆栈帧。Thread将执行的方法,原始数据类型,对象指针,返回值按其执行顺序排列到其堆栈帧。
步骤#1 : main()方法被推入应用程序线程的堆栈中。
步骤#2: a()方法被推入应用程序线程的堆栈中。在()方法中,原始数据类型“int”定义为值0并分配给变量x。此信息也被推送到同一堆栈帧中。注意两个数据,即’0’和变量’x’被推入线程的堆栈帧。
步骤#3: b()方法被推入线程的堆栈。在b()方法中,创建’Car’对象并将其赋值给变量’y’。这里要注意的关键点是’Car’对象是在堆中创建的,而不是在线程的堆栈中创建的。只有Car对象的引用,即y存储在线程的堆栈帧中。
步骤#4: c()方法被推入线程的堆栈中。在c()方法中,原始数据类型“float”定义为值0f并分配给变量z。此信息也被推送到相同的堆栈帧中。注意两个数据,即’0f’和变量’z’被推入线程的堆栈帧。
一旦每个方法的执行完成,则删除存储在堆栈帧中的方法和变量/对象指针,如下图所示。
是什么导致了StackOverflowError
正如您所看到的,线程的堆栈正在存储正在执行的方法,原始数据类型,变量,对象指针和返回值。所有这些都消耗内存。如果线程的堆栈大小超出分配的内存限制,则抛出StackOverflowError。让我们看看下面的buggy程序,它将导致StackOverflowError:1
2
3
4
5
6
7
8
9
10
11
12public class SOFDemo {
public static void a() {
// 导致出错的地方,导致a()方法无限次调用
a();
}
public static void main(String args[]) {
a();
}
}
在这个程序中,main()方法调用一个()方法。a()方法递归调用自身。此实现将导致无限次调用a()方法。在这种情况下,a()方法将无限次地添加到线程的堆栈帧中。因此,在几千次迭代之后,将超过线程的堆栈大小限制。一旦超过堆栈大小限制,它将导致’StackOverflowError’:1
2
3
4
5
6
7
8
9Exception in thread "main" java.lang.StackOverflowError
at com.buggyapp.stackoverflow.SOFDemo.a(SOFDemo.java:7)
at com.buggyapp.stackoverflow.SOFDemo.a(SOFDemo.java:7)
at com.buggyapp.stackoverflow.SOFDemo.a(SOFDemo.java:7)
at com.buggyapp.stackoverflow.SOFDemo.a(SOFDemo.java:7)
at com.buggyapp.stackoverflow.SOFDemo.a(SOFDemo.java:7)
at com.buggyapp.stackoverflow.SOFDemo.a(SOFDemo.java:7)
at com.buggyapp.stackoverflow.SOFDemo.a(SOFDemo.java:7)
at com.buggyapp.stackoverflow.SOFDemo.a(SOFDemo.java:7)
解决方案
有几种策略可以解决StackOverflowError问题。
1. 修复代码
如上错误代码所示,由于非终止递归调用,线程堆栈大小可以增长到很大直到抛出异常。在这种情况下,必须修复导致递归循环的源码。当抛出’StackOverflowError’时,从堆栈可以看出递归执行的代码地方。在上面的例子中,它是’a()’方法里递归调用了,移除递归调用或者添加停止递归的逻辑即可解决。
2. 增加堆栈大小
可能存在需要增加线程堆栈大小的合理原因。也许线程必须执行大量的方法或在方法线程中执行的许多局部变量/执行。在这种情况下,您可以使用JVM参数:’-Xss’来增加线程的堆栈大小。启动应用程序时需要传递此参数。例:1
-Xss2m
这会将线程的堆栈大小设置为2 mb。
它可能会带来一个问题,默认线程的堆栈大小是多少?默认线程堆栈大小因操作系统,Java版本和供应商而异。
JVM版本 | 线程堆栈大小 |
---|---|
Sparc 32位JVM | 512K |
Sparc 64位JVM | 1024K |
x86 Solaris / Linux 32位JVM | 320K |
x86 Solaris / Linux 64位JVM | 1024K |
Windows 32位JVM | 320K |
Windows 64位JVM | 1024K |