堆存对象、栈存变量和引用:new创建的对象和数组在堆中,由GC管理;基本类型值、对象引用及方法局部信息在栈中,随方法调用自动进出。

堆存对象,栈存变量和引用——这是最直接的区分,也是你写代码时必须心里有数的边界。
堆里放什么?new 出来的都归它管
所有用 new 创建的对象(比如 new ArrayList()、new String("abc"))、数组(int[] arr = new int[100]),全部落在堆中。堆是线程共享的,大小可调(通过 -Xms 和 -Xmx),由 JVM 的垃圾回收器(GC)自动管理。
- 对象生命周期不确定:只要还有强引用指向它,就不会被回收;引用断了,才可能在某次 GC 中被清理
- 别指望“立刻释放”:即使
list = null,内存也不会马上还给操作系统,只是标记为可回收 - 常见踩坑:
static List长期持有大对象,容易 OOM;没清空集合却反复cache = new ArrayList() add,对象越积越多
栈里放什么?方法一调用就自动配好
每个线程独享一个栈,里面只存三类东西:int/boolean 等基本类型值、对象的引用(4 或 8 字节的地址)、方法调用所需的局部变量表 + 操作数栈 + 返回地址等信息。栈帧随方法进退自动压入/弹出,不走 GC。
- 生命周期严格绑定方法:
void foo() { String s = "hello"; }中的s变量本身在栈上,但它指向的字符串对象(若非常量池)在堆上 - 栈大小固定(默认通常 1MB 左右):递归过深或局部变量巨多(如超大数组声明在栈里)会直接抛
StackOverflowError - 注意陷阱:不要在栈里声明大数组,比如
byte[] buf = new byte[1024 * 1024];—— 这行代码的buf引用在栈,但数组对象本身在堆;但如果写成byte[] buf = new byte[1024 * 1024 * 100];,大概率触发堆 OOM,不是栈溢出
为什么不能把对象放栈里?——不是不想,是不能
栈按 FILO(后进先出)组织,硬件级支持 push/pop,极快但必须确定大小和生命周期。而 Java 对象大小动态(字段可变、继承链影响布局)、存活时间不可预知(可能被多个方法长期引用)。硬塞进栈,要么限制语言表达力,要么崩溃。
立即学习“Java免费学习笔记(深入)”;
- 例外存在:JVM 的逃逸分析(Escape Analysis)可能将未逃逸的小对象分配在栈上(标量替换),但这对开发者透明,不可依赖
- 你写的
StringBuilder sb = new StringBuilder(),sb这个引用一定在栈,StringBuilder实例一定在堆——这点永远成立 - 混淆点常出现在字符串字面量:
String s = "abc"中,s在栈,"abc"在方法区(常量池),不属于堆也不属于栈
真正难的不是分清“堆存对象、栈存引用”,而是当 OutOfMemoryError: Java heap space 或 StackOverflowError 报出来时,你能一眼判断是缓存没清理、递归写错了,还是对象图太深、GC 参数不合理。这些错误背后,全是堆与栈分工逻辑的具象反馈。










