StackOverFlowError:起因与解决方案

StackOverFlowError 是最为常见的 JVM 错误之一。在本博文中,我们将了解线程栈的内部机制、触发 StackOverFlowError 的可能原因以及解决此类错误的潜在解决方案。

为了更加深入地了解 StackOverFlowError,下面就让我们来看看这一个简单程序:

public 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");
}
}

该程序非常简单,执行代码如下:

  1. 首先调用 main() 方法
  2. main() 方法调用 a() 方法。a() 方法中整数型变量‘x’被初始化为 0。
  3. a() 方法接着调用 b() 方法。b() 方法中构建了一个 Car 对象并将其分配给了变量‘y’。
  4. b() 方法接着调用 a() 方法。c() 方法中浮动变量‘z’被初始化为 0。

下面让我们来看看在上述简单程序执行时发生了什么。应用程序中的每个线程都有其栈。每个栈都有多个栈帧。线程会将其正在执行的方法、原始数据类型、对象指针、返回值按执行顺序添加到栈帧中。

图 1:线程栈帧

步骤 #1main() 方法被压入应用程序的线程栈中。

步骤 #2a() 方法被压入应用程序的线程栈中。a() 方法中使用值 0 对原始数据类型‘int’进行了定义,并将其分配给变量 x。这一信息也被压入了相同的栈帧。注意,数据(‘0’)和变量(‘x’)都被压入了线程的栈帧。

步骤 #3b() 方法被压入应用程序的线程栈中。b() 方法中创建了一个‘Car’对象并将其分配给了变量‘y’。这里要注意的关键点是,‘Car’对象是在中创建的,而不是在线程的栈中。只有 Car 对象的引用(即‘y’)被存储在了线程的栈帧中。

步骤 #4c() 方法被压入应用程序的线程栈中。c() 方法中使用值 0f 对原始数据类型‘float’进行了定义,并将其分配给变量 z。这一信息也被压入了相同的栈帧。注意,数据(‘0f’)和变量(‘z’)都被压入了线程的栈帧。

每个方法执行完成后,方法和存储在栈帧中的变量/对象指针都将被移除。如图 2 所示。

图 2:方法执行后的线程栈

导致 StackOverflowError 的原因是什么?

相信您也看到了,线程的栈中存储了其所执行的方法、原始数据类型、变量、对象指针以及返回值。这些都会消耗内存。如果线程的栈内存大小超出了上限,就会抛出 StackOverflowError。我们来看看下面这一存在问题的程序,其最终抛出了 StackOverflowError:

public class SOFDemo {
public static void a() {
// Buggy line.It will cause method a() to be called infinite number of times.
a();
}
public static void main(String args[]) {
a();
}
}

在这一简单程序中,main() 方法调用了 a() 方法。a() 则递归调用着其自己。此实现会导致一个 a() 方法被调用无限次。在此类情形中,a() 方法将被无限次添加至线程的栈帧中。因此,几千次迭代后将超出线程的栈大小。在超出了栈大小的上限后,就出现了‘StackOverflowError’:

Exception 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)

图 3:StackOverflowError 进展

StackOverflowError 的解决方案有哪些?

解决 StackOverflowError 的策略有几种。

1. 修复代码

由于非终止调用(如上所示),线程栈的大小将会无限增长。在此类情形中,您必须修复导致递归循环的源代码。在抛出‘StackOverflowError’时,其将输出递归执行代码的栈追踪信息。此处的代码就是开始问题调试和修复的绝佳指示器。在上方的示例中,这个指示器是‘a()’方法。

2. 增加线程栈内存大小(-Xss

增加线程栈内存大小的理由可能会有许多。可能是线程必须执行大量的方法,或者在线程执行的方法中创建了很多局部变量。在这种情况下,您可以使用 JVM 参数来增加线程的栈大小:‘-Xss’。此参数需要在启动应用程序时传递。示例:

-Xss2m

其会将线程的栈大小设为 2 MB。

但这可能会带来一个问题:默认线程的栈内存大小如何?默认线程栈大小将因您的操作系统、Java 版本和供应商而异。

JVM 版本线程栈大小
Sparc 32-bit JVM512k
Sparc 64-bit JVM1024k
x86 Solaris/Linux 32-bit JVM320K
x86 Solaris/Linux 64-bit JVM1024K
Windows 32-bit JVM320K
Windows 64-bit JVM1024K

其他参考资料

java.lang.StackOverflowError – 如何解决 StackOverflowError

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Blog at WordPress.com.

Up ↑

%d bloggers like this: