栈里存地址,堆里存对象本身;局部变量(含引用)在栈中仅存储指向堆对象的地址,对象实例及其所有成员变量(含基本类型和引用类型字段)完整存在于堆中。

Java面向对象中,对象在堆中分配、引用变量在栈中指向,核心就一句话:**栈里存地址,堆里存对象本身**。不是“栈存引用、堆存数据”这种模糊说法,而是明确——栈中那个变量(比如 Person p)只占4或8字节,里面写的是一串内存地址;而真正的 Person 实例(含 name、age、addr 等所有字段)完整地躺在堆里。
对象一创建,整块结构就落堆里
只要用了 new,JVM 就会在堆中划出连续空间,把对象头、所有实例字段(包括 int、String 引用、甚至另一个 new 出来的对象)全部打包进去。比如:
-
class Person { String name; Address addr; }→ 整个Person对象(含name字段的引用值、addr字段的引用值)都在堆中 -
addr字段本身是个引用,它指向堆中另一个Address对象 —— 两个对象彼此独立,但都在堆里 - 字段里的基本类型(如
int age)直接作为对象数据的一部分,也存在堆中,不单独上栈
引用变量只是栈里的“小标签”
像 Person p = new Person(); 这行代码中,p 是局部变量,它存在于当前方法的栈帧里。这个 p 不是对象,也不是对象的副本,它就是一个轻量级地址值:
- 方法执行完,栈帧弹出,
p消失 → 但堆里的Person实例还在,只要还有别的变量(比如成员变量、静态集合)指着它 - 你可以写
Person q = p;→ 栈里多了一个q,存的是和p一样的地址,二者指向堆中同一个对象 - 后续调用
q.setName("Bob"),改的是堆中那个对象的name字段,p.getName()自然也返回 "Bob"
为什么修改对象内容,外面能看见?
因为形参接收的,是实参引用的“地址副本”,不是对象副本。方法内通过这个地址去堆里操作,自然影响原对象:
立即学习“Java免费学习笔记(深入)”;
-
void append(List<String> list) { list.add("x"); }→list参数在栈里,但它指向的ArrayList实例在堆中,add改的是堆里的数组和 size 字段 - 但如果在方法里写
list = new ArrayList<>();→ 只是让形参list指向新堆地址,原始引用没变,调用方完全无感 - String 类型看似例外,是因为
s = s + "a"实际是创建新 String 对象并让引用指向它,原对象(如果没其他引用)就成垃圾了
判断对象会不会被回收,只看有没有“路”能走到它
GC Roots(如栈中的引用、静态变量、JNI 引用)出发,沿着引用链能到达的对象就是“可达”的;断掉所有路径,对象就进回收队列:
- 局部变量超出作用域 → 栈中引用消失,若再无其他引用,对象立刻不可达
- 显式赋
obj = null→ 主动切断一条路径,帮助 GC 提前识别 - 放进
static Map<K, V>却忘了移除 → 引用长期存在,对象一直活在堆里,容易内存泄漏 - WeakReference / SoftReference 不构成强引用链,不影响 GC 决策逻辑


















