Java对象内存分配中,对象实例(含字段数据)一定在堆中,引用变量通常在栈中;但静态字段、类元数据存于元空间,逃逸分析可能将未逃逸对象优化至栈上分配。

Java 中对象的内存分配不是“对象整体放哪”,而是按角色拆解:对象本身(实例数据)一定在堆中,而它的引用(即变量名)通常在栈上——但这个“通常”有关键例外。
栈上存什么:引用和局部变量,不是对象
栈只保存两类东西:基本类型值(如 int、boolean)和对象引用(如 String s 中的 s 本身)。这个引用是一个 4 字节或 8 字节的地址值,它指向堆里的真实对象。方法调用结束,栈帧弹出,引用消失,但堆中的对象不会自动销毁——只要还有其他引用指向它,它就继续存在。
例如:
- String s = "hello"; → 栈上存引用 s,字面量 "hello" 在运行时常量池(属于堆)
- String s = new String("hello"); → 栈上仍只存引用 s,堆中新建一个 String 实例(常量池里已有 "hello",但新对象是独立的)
- int x = 10; → x 和 10 都直接在栈上,不涉及堆
堆上存什么:所有 new 出来的实例和数组
凡是通过 new 创建的对象、数组,包括 new ArrayList()、new int[100]、new Person(),其完整的实例数据(字段内容、数组元素)都落在堆中。堆是线程共享的,GC 管理的区域,大小由 -Xms 和 -Xmx 控制。
注意两个细节:
- 数组也是对象,所以 int[] arr = new int[5]; 中,arr 引用在栈,5 个 int 元素 在堆
- 静态字段(static)和类元数据(类名、方法字节码等)不在堆或栈,而在元空间(Metaspace)
逃逸分析可能让对象“不落地”
JVM(HotSpot)在运行时会做逃逸分析:如果一个对象只在当前方法内使用,没被返回、没被赋给静态/成员变量、也没传给外部方法,JVM 可能跳过堆分配,直接把它的字段拆开,作为局部变量放在栈上(标量替换)。但这完全由 JVM 自动决定,开发者无法控制或保证。
比如:
- return new StringBuilder(); → 对象逃逸,一定进堆
- StringBuilder sb = new StringBuilder(); sb.append("a"); → 若后续没逃逸,JVM 可能优化为栈上操作
- 这种优化对小对象更常见;含锁、同步块、复杂字段的对象基本不会被优化
参数传递体现栈与堆的协作
方法调用时,参数进栈的方式取决于类型:
- 基本类型参数:值拷贝,栈上独立一份,修改不影响原值
- 引用类型参数:拷贝的是引用(地址),栈上新增一个指向堆中同一对象的指针;方法内通过该引用修改对象状态,会影响原对象
这说明栈和堆始终协同工作:栈负责快速调度和生命周期管理,堆负责承载实际数据和跨作用域共享。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











