局部变量存储在当前线程的java虚拟机栈中,具体位于所属方法的栈帧局部变量表内,不存于堆中;基本类型值直接存放,引用类型存放指向堆中对象的指针。

局部变量到底存在哪?栈帧里还是堆里?
局部变量只存在于当前线程的 Java 虚拟机栈中,具体是所在方法的栈帧(Stack Frame)里的局部变量表(Local Variable Table)。它不进堆,也不跨方法存活。
常见错误现象:NullPointerException 却发现变量明明“刚 new 过”,其实是局部变量引用被覆盖或方法已返回,栈帧销毁,引用失效——不是对象丢了,是“指针”没了。
-
int x = 42;:值直接存栈帧的局部变量表(基本类型) -
String s = new String("hi");:引用s存栈帧,new String("hi")对象本身在堆中 - 方法参数也是局部变量,同样走栈帧,哪怕传的是对象引用
- 局部变量表索引可能复用(如 for 循环结束后定义新变量),所以栈空间不一定随代码行数线性增长
成员变量(字段)为什么一定在堆里?
因为成员变量属于对象实例的一部分,而所有对象实例都分配在堆内存。JVM 在为对象分配内存时,会把实例变量(非 static)连同对象头、对齐填充一起打包进堆块。
使用场景:当你看到 this.field 或 obj.field,这个 field 的值(或引用)就嵌在堆中那个对象的内存布局里,不是独立存在。
-
private int count;:作为对象结构的一部分,和对象生命周期一致 -
private List<string> items = new ArrayList();</string>:引用items存在堆中对象体内,new ArrayList()对象也在堆中(可能不同内存块) - static 字段例外:它存在方法区(JDK 8+ 是元空间),不属于任何对象实例,也不在堆中
- 注意逃逸分析:若 JVM 判定某个对象不会逃逸出方法,可能栈上分配(标量替换),但这是优化,不影响语义——你写的成员变量逻辑仍按堆中处理
怎么验证变量到底在哪儿?用 jol 看内存布局
光靠理论容易绕晕,用 JOL(Java Object Layout)工具能直接打印对象在堆中的内存分布,包括字段偏移、大小、是否被压缩指针影响。
实操建议:别猜,用工具看。尤其当涉及内存占用估算、缓存行对齐、或排查 GC 行为时,真实布局比语言规范更管用。
- 加依赖:
org.openjdk.jol:jol-core - 运行
ClassLayout.parseClass(MyClass.class).toPrintable(),输出里能看到每个字段的 offset 和 size - 局部变量无法用 jol 查——它只作用于堆中对象,这也反向印证了局部变量不在堆里
- 注意 JDK 版本差异:JDK 15+ 默认开启压缩类指针(-XX:+UseCompressedClassPointers),会影响字段对齐,导致偏移不是直觉上的累加
为什么 static 变量和局部变量都不能被 GC 直接回收?
它们根本不在 GC Roots 的可达路径里“参与竞争”——不是 GC 不想收,是压根没资格进 GC 的扫描范围。
局部变量:方法结束 → 栈帧弹出 → 引用消失 → 如果没有其他强引用指向堆中对象,那对象才变成可回收状态;static 变量:作为类的元数据一部分,只要类没卸载(几乎永不发生),它的静态字段引用就一直有效。
- 局部变量表本身不被 GC 管理——栈内存由线程生命周期自动管理
- static 字段引用的对象,只要类加载器还活着,GC 就认为它“永远可达”
- 容易踩的坑:
static Map<string object> cache = new HashMap();</string>导致内存泄漏,不是因为 static 本身危险,而是它无意中延长了对象生命周期 - 局部变量如果持有大对象引用且方法执行时间长(比如 long-running loop),也可能阻塞 GC——虽然变量在栈上,但它挡住了堆对象的回收时机










