java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java StackOverflowError原理

Java中的StackOverflowError从原理到实战指南(栈溢出的 4 种典型场景)

作者:会员源码网

java.lang.StackOverflowError 是 JVM 抛出的错误(Error) ,不是普通异常(Exception),本文给大家介绍Java中的StackOverflowError从原理到实战指南(栈溢出的 4 种典型场景),感兴趣的朋友跟随小编一起看看吧

一、什么是 StackOverflowError?

java.lang.StackOverflowError 是 JVM 抛出的错误(Error) ,不是普通异常(Exception)。它表示:当前线程的虚拟机栈空间被耗尽,无法再分配新的栈帧

核心原理

  1. 每个线程有独立的虚拟机栈,默认大小约 1MB(可通过 -Xss 调整)。
  2. 每调用一个方法,JVM 会创建一个栈帧压入栈中,存放局部变量、返回地址、操作数栈等。
  3. 方法执行完毕,栈帧出栈。
  4. 压栈速度远大于出栈,栈空间被占满,就抛出 StackOverflowError。

一句话总结:方法调用层级太深,栈被撑爆了。

二、栈溢出的 4 种典型场景(附代码)

1. 无限递归(最常见)

没有正确的递归出口,方法无限调用自身。

public class StackOverflowDemo {
    public static void recursion() {
        recursion(); // 无终止条件
    }
    public static void main(String[] args) {
        recursion();
    }
}

报错:直接抛出 StackOverflowError,堆栈信息疯狂重复同一行。

2. 递归深度过大(有出口但太深)

public static int deepRecursion(int n) {
    if (n == 0) return 0;
    return deepRecursion(n - 1);
}
// 调用:deepRecursion(100000) → 栈溢出

默认栈深度大约在 1000~2000 层,超过就会爆。

3. 方法循环调用(A→B→A→B…)

public class CycleCall {
    public static void a() { b(); }
    public static void b() { a(); }
    public static void main(String[] args) { a(); }
}

本质和无限递归一样,形成调用环。

4. 超大局部变量(少见但致命)

栈不仅存调用链,还存局部变量。一次性在栈上分配超大数组会直接爆栈。

public static void bigLocalVar() {
    int[] huge = new int[1024 * 1024]; // 栈上分配,直接溢出
}

注意:对象实例在堆,数组引用在栈,但超大基本类型数组会占满栈空间。

三、关键知识点:Error vs Exception

四、如何快速定位 StackOverflowError?

1. 看异常堆栈(最直接)

堆栈信息会大量重复某几行,那就是递归 / 循环调用的根源。示例:

plaintext

Exception in thread "main" java.lang.StackOverflowError
at com.demo.StackOverflowDemo.recursion(StackOverflowDemo.java:5)
at com.demo.StackOverflowDemo.recursion(StackOverflowDemo.java:5)
at com.demo.StackOverflowDemo.recursion(StackOverflowDemo.java:5)
...

2. 用 jstack 抓线程栈

jstack -l <pid>

找到 RUNNABLE 且栈超长的线程,定位问题方法。

3. IDE 断点调试

在可疑递归 / 循环处打断点,观察调用次数与条件是否正常收敛。

五、解决方案:从根本到临时

1. 修复递归(首选)

// 正确递归示例
public static int factorial(int n) {
    if (n <= 1) return 1; // 出口
    return n * factorial(n - 1); // 收敛
}

2. 递归改迭代 / 循环(根治深度问题)

用循环 + 集合模拟栈,彻底摆脱栈深度限制。

// 迭代替代递归
public static int loopFactorial(int n) {
    int res = 1;
    for (int i = 2; i <= n; i++) res *= i;
    return res;
}

3. 增大栈空间(临时方案)

通过 JVM 参数 -Xss 调整线程栈大小:

-Xss256k
-Xss1m
-Xss2m

不建议滥用:栈过大会导致线程数减少,引发 OOM,只能作为临时兼容。

4. 解除循环调用

梳理依赖关系,避免 A↔B 无限调用。

5. 大对象移到堆

把栈上超大局部变量改成对象,分配到堆。

六、栈溢出 vs 堆溢出(OOM)

表格

对比项StackOverflowErrorOutOfMemoryError
发生区域虚拟机栈堆 / 方法区 / 直接内存
原因方法调用太深、栈帧太多堆内存不足,无法分配对象
触发场景递归、循环调用内存泄漏、大对象、加载类过多
JVM 参数-Xss-Xms -Xmx

七、企业级避坑建议

  1. 慎用递归:能用迭代就不用递归。
  2. 递归必加出口与深度限制
  3. 禁止在栈上声明超大数组
  4. 框架使用注意:MyBatis 嵌套查询、Spring AOP 嵌套切面、Lombok 构造循环都可能隐式递归。
  5. 上线前压测:极端数据(深度树、超长链)必测。

八、总结

StackOverflowError 本质就是栈空间被方法调用占满

解决思路:先看堆栈定位重复代码 → 修复递归 / 解除循环 → 必要时调整 -Xss

只要掌握原理与排查流程,栈溢出就是最容易解决的错误之一。

到此这篇关于Java中的StackOverflowError从原理到实战指南(栈溢出的 4 种典型场景)的文章就介绍到这了,更多相关Java StackOverflowError原理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文