stackoverflowerror 是线程栈被撑满导致的,源于无退出条件的递归或隐式循环调用(如 tostring/equals 中双向引用),排查应聚焦堆栈顶部重复方法、参数变化规律及反射/序列化触发点,优先修复逻辑而非调大 -xss。

StackOverflowError 不是内存泄漏,也不是堆空间不足,而是线程栈被撑满——每个方法调用都会压入一个栈帧,帧太多就溢出。复现容易,排查关键在“看哪里反复出现”,而不是盲目调大 -Xss。
怎么快速复现 StackOverflowError
写一段没有退出条件的递归,几行代码就能触发:
- 定义一个方法,内部直接调用自己(如
void loop() { loop(); }) - 确保不加任何 if 判断或参数变化逻辑
- 启动后几毫秒内就会抛错,控制台输出大量重复方法名
更贴近实际的复现场景包括:
- 重写了
toString(),但字符串拼接中包含了自身对象(如"User{" + this.profile + "}",而profile.toString()又返回含User的字符串) -
equals()方法里没判空或类型,直接用了other.field.equals(this),形成隐式循环 - Lombok
@Data生成的toString遇到双向关联对象(如 A 持有 B,B 又持有 A)
看堆栈日志,定位真实入口
异常抛出时打印的堆栈不是“从哪开始”的线索,而是“卡在哪”的证据。重点看顶部 20–50 行:
- 如果连续几十行都是同一个方法名(比如
parseJson()、renderTemplate()),基本就是它在无限展开 - 观察参数值是否在规律变化:如
factorial(100)→factorial(99)→factorial(98)… 却始终没到factorial(0),说明终止条件写错了(比如漏了负数判断) - 注意调用链中是否穿插了
toString、hashCode、writeValueAsString这类易触发反射/序列化的操作——它们常是隐式递归的“放大器”
用工具辅助验证,别只靠猜
本地开发环境可直接用 IDE 调试;线上服务则依赖命令行工具:
- 加 JVM 参数
-XX:MaxJavaStackTraceDepth=5000,避免默认截断(尤其老 JDK 版本) - 出错瞬间执行
jstack -l <pid></pid>,搜索RUNNABLE状态下深度超 800 层的线程,对比正常线程(通常 100–300 层) - 若怀疑是某段逻辑引发,临时加日志:
System.out.println("depth=" + Thread.currentThread().getStackTrace().length); - 不推荐一上来就改
-Xss;先用jstack抓几个正常线程,算平均栈深——如果问题线程才比平均深 100 层,那大概率是代码问题,不是栈小
修复思路要分清主次
栈溢出本质是逻辑失控,不是资源不够。优先级顺序很明确:
- 先检查递归是否有 base case,且该 case 是否真能被触达(比如参数越界后没进 if)
- 重写
toString/equals时,避免直接引用可能循环的对象字段;改用 ID、名称等简单属性 - 对已知深度大的场景(如解析嵌套 JSON、遍历 AST),用
Deque或ArrayDeque手动维护状态,把调用栈搬到堆上 - 仅当确认业务确实需要极深调用(如千层 XML 解析),再考虑临时加大
-Xss验证,但上线前必须回归代码逻辑
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











