栈内存只存方法调用的局部状态,每个栈帧含局部变量表、操作数栈、动态链接和返回地址;对象实例、数组、字符串常量均不在栈中,而分别存于堆或方法区。

栈内存存的是方法调用的「现场快照」
Java 的栈内存(Java Virtual Machine Stack)不存对象本身,也不存全局配置或静态变量——它只保存每个线程正在执行的方法调用所需的**局部状态**。每次调用方法,JVM 就压入一个栈帧(Stack Frame),方法返回时自动弹出。这个机制决定了栈是线程私有、后进先出、生命周期严格绑定方法执行周期的。
每个栈帧里具体放这四类东西
一个栈帧由四部分组成,缺一不可:
-
局部变量表(Local Variable Table):存放方法参数和方法内定义的局部变量,包括int、long、reference(即对象引用,不是对象)、returnAddress等。注意:long和double占两个槽位 -
操作数栈(Operand Stack):JVM 字节码指令的工作台,比如iload_0把局部变量表第 0 个 int 压栈,iadd弹出栈顶两个 int 相加再压回 -
动态链接(Dynamic Linking):指向运行时常量池中该方法的符号引用,用于支持多态和反射调用(如invokedynamic) -
方法返回地址(Return Address):记录方法执行完后要跳转回的字节码位置(即调用点下一条指令),也负责处理异常出口
常见误解:String、new Object()、数组到底存在哪?
这是最容易混淆的地方:
- 声明
String s = "hello";→ 引用s存在栈帧的局部变量表里;字符串常量"hello"存在方法区(JDK 7+ 的运行时常量池,属于堆逻辑分区) - 执行
Object obj = new Object();→ 引用obj存在栈帧中;真正的Object实例分配在堆内存(Heap) - 声明
int[] arr = new int[10];→ 引用arr在栈;数组对象本身(含 10 个int元素)在堆 - 递归过深导致
java.lang.StackOverflowError,本质是栈帧太多,超出了-Xss设置的单线程栈大小(默认通常 1MB)
栈大小设置与调试验证方法
栈空间不是越大越好,也不是越小越省——它直接影响可并发线程数和单线程最大调用深度:
立即学习“Java免费学习笔记(深入)”;
# 启动时限制单线程栈大小为 256KB(比默认更小,便于复现栈溢出) java -Xss256k MyApp查看某个线程栈帧详情(需在 debug 模式或使用 jstack)
jstack
| grep -A 10 "java.lang.Thread.run"
注意:-Xss 值设得太小,可能连 Spring Boot 启动都失败(大量代理类 + AOP 导致调用链深);设得太大,在高并发场景下容易因线程过多耗尽内存。实际项目中建议保持默认,仅在明确遇到 StackOverflowError 且确认非死递归时才调整。
栈帧的结构和生命周期是 JVM 执行模型的底层锚点,很多看似无关的问题——比如 Lambda 表达式捕获变量的 final 语义、try-with-resources 的编译器重写、甚至某些 JIT 内联决策——背后都依赖这套栈管理规则。真正卡住的时候,往往不是代码写错了,而是没意识到某个引用还在栈里等着被弹出。










