栈存引用、堆存实体,引用指向堆中对象且单向存在;基本类型值存栈,引用类型栈存指针、对象字段全在堆;多线程下引用关系成立但需JMM保障可见性。

堆和栈之间不是独立存在的,而是通过“引用”紧密连接的。这种关系是Java程序运行的基础逻辑:栈里存的是“指针”,堆里存的是“实体”。
栈中只存对象引用,不存对象本身
当你写 String s = new String("hello"); 时:
- s 这个变量(包括它的类型、值)存放在当前线程的栈帧局部变量表中;
- new String("hello") 创建的对象实际内存分配在堆上;
- s 的值本质上是一个内存地址(比如 0x1a2b3c),指向堆中那个字符串对象的起始位置。
引用是单向的,且生命周期不同步
栈中的引用变量可以随时消失,但堆中的对象不会立刻销毁:
- 方法执行结束 → 栈帧出栈 → 引用变量 s 消失;
- 如果此时没有其他引用指向该字符串对象,它就变成“不可达对象”,等待 GC 回收;
- 如果还有 static 字段、集合容器或其它线程中的变量持有着这个对象的引用,它就会继续留在堆中。
基本类型和引用类型的存储差异明显
这进一步体现了栈与堆的分工:
立即学习“Java免费学习笔记(深入)”;
- int、boolean、char 等基本类型的局部变量,值直接存在栈帧的局部变量表里;
- String、List、Person 等引用类型的局部变量,栈里只存一个 4 字节(32 位 JVM)或 8 字节(64 位 JVM,开启压缩指针后仍为 4 字节)的引用;
- 对象的实际字段(如 Person.name、Person.age)全部落在堆中,哪怕 age 是 int 类型——它是对象的一部分,不是局部变量。
多线程下引用关系依然成立,但需注意可见性
虽然堆是线程共享的,栈是线程私有的,但引用关系不受影响:
- 线程 A 在堆中创建了一个对象,并把引用赋给自己的局部变量;
- 若该引用被存入一个静态集合(如 public static List<Object> cache),线程 B 就能通过这个集合访问到堆中同一个对象;
- 但线程 B 读到的字段值是否是最新的,取决于 Java 内存模型(JMM)的可见性规则——这需要 volatile、synchronized 或 final 等机制保障,而不是引用本身能解决的问题。


















