StackOverFlowError的原因和解决方案

StackOverFlowError 是常见的 JVM 错误之一。在这篇文章中,我将带大家了解线程堆栈的内部机制、触发 StackOverFlowError 的原因以及解决此错误的潜在解决方案。

首先先看一下下面这个简单的程序:

csharp 复制代码
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() 方法依次调用 c() 方法。在 c() 方法中,浮点变量z被初始化为值 0

现在,让我们看看执行上述简单程序时底层发生的情况。应用程序中的每个线程都有自己的堆栈。每个堆栈有多个堆栈帧。线程将其正在执行的方法、原始数据类型、对象指针以及返回值按照它们执行的顺序添加到其堆栈帧中。

线程栈帧

step1:main() 方法被推入应用程序线程的堆栈中。

step2:a() 方法被推入应用程序线程的堆栈中。在 a() 方法中,原始数据类型"int"定义为值 0 并分配给变量 x。该信息也被推送到同一堆栈帧中。请注意,这两个数据(即"0"和变量"x")都被推入线程的堆栈帧中。

step3:b()方法被推入线程的堆栈中。在 b() 方法中,创建 Car 对象并将其分配给变量"y"。这里需要注意的一个关键点是"Car"对象是在堆中创建的,而不是在线程的堆栈中创建的。只有 Car 对象的引用(即 y)存储在线程的堆栈帧中。

step4:c() 方法被推入线程的堆栈中。在 c() 方法中,原始数据类型"float"被定义为值 0f 并分配给变量 z。该信息也被推送到同一堆栈帧中。请注意,这两个数据(即"0f"和变量"z")都被推入线程的堆栈帧中。

每个方法执行完成后,该方法和存储在堆栈帧中的变量/对象指针将被删除。结果如下图

线程栈帧

为什么会导致StackOverFlowError?

正如上面所分析的,线程的堆栈存储它正在执行的方法、原始数据类型、变量、对象指针和返回值。所有这些都会消耗内存。如果线程的堆栈大小超出了分配的内存限制,则会引发 StackOverflowError。

让我们看一下下面的错误程序

typescript 复制代码
public class Demo {
     public static void a() {
       //无限递归
          a();
     }
     public static void main(String args[]) {
          a();
     }
}

在此程序中,main() 方法调用 a() 方法。 a() 方法无限递归调用自身。这将导致 a() 方法被无限次调用。在这种情况下,a() 方法将被无限次添加到线程的堆栈帧中。

因此,经过几千次迭代后,将超出线程的堆栈大小限制。一旦超出堆栈大小限制,就会导致 StackOverflowError:

php 复制代码
Exception in thread "main" java.lang.StackOverflowError
	at com.zxy.demo.test.Demo.a(Demo.java:8)
	at com.zxy.demo.test.Demo.a(Demo.java:8)
	at com.zxy.demo.test.Demo.a(Demo.java:8)
	at com.zxy.demo.test.Demo.a(Demo.java:8)
	at com.zxy.demo.test.Demo.a(Demo.java:8)
	at com.zxy.demo.test.Demo.a(Demo.java:8)

怎么解决StackOverFlowError?

有几种方法可以解决 StackOverflowError。

1. 修复代码

由于没有终止的递归调用(如上例所示),线程堆栈大小可能会增长到很大。在这些情况下,您必须修复导致递归循环的源代码。当抛出 StackOverflowError 时,它将打印递归执行的代码的堆栈跟踪。

此代码是开始调试和解决问题的良好指针。在上面的示例中,它是 a() 方法。

2.增加线程堆栈大小(-Xss)

需要增加线程的堆栈大小可能有合理的原因。也许线程必须执行大量方法,或者线程正在执行的方法中/创建的大量局部变量。在这种情况下,我们可以使用 JVM 参数:-Xss 来增加线程的堆栈大小。启动应用程序时需要传递此参数。

例子:-Xss2m

这会将线程的堆栈大小设置为 2mb。 这里可能会有一个问题:默认线程的堆栈大小是多少?默认线程堆栈大小因操作系统、Java 版本和供应商而异。

JVM version Thread stack size
Sparc 32-bit JVM 512k
Sparc 64-bit JVM 1024k
x86 Solaris/Linux 32-bit JVM 320K
x86 Solaris/Linux 64-bit JVM 1024K
Windows 32-bit JVM 320K
Windows 64-bit JVM 1024K

3.使用具有自定义堆栈大小的线程

解决StackOverflowError 的另一种方法是利用 Java 的线程构造函数,它允许您为各个线程指定自定义堆栈大小。这个构造函数可以在 Java 文档中找到。虽然此选项可以灵活地为每个线程设置特定的堆栈大小,但需要注意的是,其有效性可能会因平台的不同而有所不同。

ini 复制代码
Thread thread = new Thread(null, runnable, "CustomThread", customStackSize); 
thread.start();

我们要意识到设置 stackSize 参数的影响可能不会 在所有平台上保持一致。 Java 文档指出:"在某些平台上,stackSize 参数的值可能没有任何影响。虚拟机可以自由地将 stackSize 参数视​为建议。" 在之前的测试中,我们发现使用自定义堆栈大小调用此构造函数对 Windows 和某些其他平台没有影响。跨平台缺乏一致性使得该选项作为通用解决方案不太可靠。作为最佳实践,建议选择在所有平台上一致工作的解决方案,以确保应用程序的稳定性和可靠性。

如果喜欢这篇文章,点赞支持一下,微信搜索:京城小人物,关注我第一时间查看更多内容!感谢支持!

相关推荐
P7进阶路1 小时前
Tomcat异常日志中文乱码怎么解决
java·tomcat·firefox
Ai 编码助手1 小时前
在 Go 语言中如何高效地处理集合
开发语言·后端·golang
小丁爱养花1 小时前
Spring MVC:HTTP 请求的参数传递2.0
java·后端·spring
CodeClimb1 小时前
【华为OD-E卷 - 第k个排列 100分(python、java、c++、js、c)】
java·javascript·c++·python·华为od
等一场春雨2 小时前
Java设计模式 九 桥接模式 (Bridge Pattern)
java·设计模式·桥接模式
Channing Lewis2 小时前
什么是 Flask 的蓝图(Blueprint)
后端·python·flask
带刺的坐椅2 小时前
[Java] Solon 框架的三大核心组件之一插件扩展体系
java·ioc·solon·plugin·aop·handler
不惑_3 小时前
深度学习 · 手撕 DeepLearning4J ,用Java实现手写数字识别 (附UI效果展示)
java·深度学习·ui
费曼乐园3 小时前
Kafka中bin目录下面kafka-run-class.sh脚本中的JAVA_HOME
java·kafka
轩辕烨瑾3 小时前
C#语言的区块链
开发语言·后端·golang