虚拟机栈与堆的交互本质是栈存地址、堆存对象,引用变量无论位于栈、堆或方法区,均只保存堆中对象的虚拟地址;赋值、传参、== 比较均为地址操作;对象头还包含指向方法区类元数据的反向指针。

虚拟机栈与堆的交互,核心就一句话:栈存“地址”,堆存“东西”,二者靠这个地址连起来。
栈里只放引用值,不是对象本身
当你写 User u = new User();,JVM 做两件事:
- 在堆中划出一块内存,创建完整的 User 实例(含字段、对象头等),并返回它的起始虚拟地址(比如 0x00007f8a12345000);
- 在当前线程的虚拟机栈帧局部变量表中,为变量 u 分配一个槽位,把那个地址值原样存进去——它只是个数字,4 字节或 8 字节,不包含任何对象数据。
也就是说,栈从不存对象,只存指向堆里某处的“路标”。哪怕对象只有一个 int 字段,也照旧在堆里;栈上永远只有那个“路标”。
引用变量可能不在栈上,但指向关系不变
不是所有引用变量都待在栈里,但它们的指向逻辑一致:
- 局部变量(方法内定义)→ 存在栈帧局部变量表中;
- 实例变量(成员变量)→ 随所属对象一起存在堆中,即“引用本身”是对象数据的一部分;
- 静态变量→ 引用存于方法区(JDK 8+ 是元空间),仍指向堆中的对象;
- ThreadLocal 的 value→ 逻辑线程私有,物理上仍存在堆中,只是通过 ThreadLocalMap 间接引用。
无论存在哪,只要它是引用类型变量,它保存的始终是堆中某个对象的虚拟地址。
赋值、传参、== 比较,本质都是地址操作
理解这三点,就能看清交互的底层动作:
- 赋值(u = v):把 v 槽位里的地址值复制给 u 槽位,两个变量指向堆中同一块内存;
- 方法传参(method(u)):把 u 的地址值压入新栈帧,形参获得的是副本,修改形参引用本身(如 u = new User())不影响原变量;
- == 比较:直接比两个槽位里存的地址是否相等,跟对象内容无关;equals 才去堆里读字段做内容比对。
对象头里还藏着反向指针
堆中的每个对象,对象头里都嵌着一个指针,指向方法区中对应的类元数据(如 Person.class 的结构信息)。这不是栈→堆的指向,而是堆→方法区的关联,用于支撑多态、类型检查、反射等机制。它和栈中引用共同构成完整定位链:栈 → 堆对象 → 方法区类定义。















