栈内存存储局部变量表(含基本类型和对象引用)、操作数栈、动态链接与返回地址,不存对象实例;栈帧过深、调用链失控或递归无终止易引发stackoverflowerror,优化应聚焦方法结构与线程模型而非堆参数。

线程私有区域里的栈内存,不参与垃圾回收,也不跨线程共享,天生轻量——但它不是“随便用”的。真正影响性能的,往往不是堆上对象太多,而是栈帧太深、局部变量表太满、或者方法调用链失控。
栈内存到底存什么?别把对象实例放进来
虚拟机栈里每个方法调用都会生成一个栈帧,里面包含:
- 局部变量表:只存基本类型(int、boolean等)和对象引用(4或8字节指针),不存对象本体
- 操作数栈:临时计算用,生命周期极短
- 动态链接与返回地址:支撑方法调用跳转
常见误区:以为在方法里 new 一个对象,这个对象就进栈了。其实它一定分配在堆上,栈里只留一个引用。栈本身不会因对象大小膨胀,但引用多了、方法嵌套深了,就会触碰栈深度限制。
栈溢出(StackOverflowError)的典型场景
这不是内存不够,而是调用链超出 JVM 允许的默认深度(通常 1000–2000 层)。高频诱因有:
- 递归没设终止条件,或终止逻辑有缺陷
- A 调 B、B 调 C、C 又回调 A 的隐式循环调用
- 框架代理层层增强(如 Spring AOP 过度织入),导致实际调用链远超代码表象
- 字节码增强工具(如 Byte Buddy、ASM)意外插入过多栈操作
可控优化点:从参数到写法
栈空间由 -Xss 参数控制(如 -Xss512k),但盲目调大治标不治本。更有效的做法是:
- 用迭代替代深度递归,尤其在树遍历、解析器、状态机等场景
- 检查日志、监控或 Arthas trace,定位深层调用链源头
- 避免在栈帧中声明大数组(如 byte[1024*1024]),虽是局部变量,但数组本身仍在堆上;真正危险的是大量小数组+高并发线程,会快速耗尽线程数上限
- 使用 Loom 的虚拟线程时,栈内存按需分配且极轻量,天然缓解传统栈压力,但需确认 JDK 版本与框架兼容性
和堆优化的关系:别混为一谈
栈内存不归 GC 管,也不会触发 Full GC;栈溢出是线程级崩溃,不是内存泄漏。优化栈的关键是控制执行路径复杂度,而不是调优 GC 参数或堆大小。两者关注维度不同:
- 堆优化看对象生命周期、引用关系、GC 停顿
- 栈优化看方法结构、调用深度、线程模型
搞清这点,才能避免在 -Xmx 上猛加内存,却对频繁 StackOverflow 视而不见。










