关键在于“谁存什么、怎么活、给谁用”:栈存基本类型值、对象引用和方法上下文,方法结束即销毁,线程私有;堆存new对象、数组和字符串常量,由gc异步回收,线程共享。

理解 JVM 堆和栈的区别,关键不是背概念,而是看清楚“谁存什么、怎么活、给谁用”这三件事。
存什么:数据类型和语义职责完全不同
栈里只放轻量、确定、临时的东西:
- 基本类型值本身(int、boolean、double等)
- 对象或数组的引用地址(比如 String s 中的 s,它只是个指针,不是字符串内容)
- 方法调用的上下文:参数、局部变量、返回地址、栈帧状态
堆里只放实体、动态、共享的东西:
- 所有 new 出来的对象(ArrayList、Person、包装类实例等)
- 所有数组(int[]、String[],不管元素是基本类型还是引用)
- 字符串常量池中的字符串对象(如 "hello",JDK 7+ 起也在堆中)
怎么管:生命周期由两套完全不同的机制控制
栈靠方法执行流程自动驱动:
- 方法开始 → JVM 压入一个栈帧
- 方法结束 → 栈帧立刻弹出 → 所有内容瞬间销毁,零延迟、零开销
- 没有 GC,也不需要 GC 参与
堆靠可达性分析 + 垃圾回收器异步管理:
- 对象创建后长期存在,直到没有任何强引用指向它
- GC 在后台周期性扫描、标记、清理,时机不可控
- 你写 list = null 或让引用超出作用域,只是“允许回收”,不是“立刻回收”
给谁用:线程视角决定并发行为
栈内存是每个线程独占的私有空间:
- 主线程、子线程各有自己的栈,互不干扰
- 局部变量天然线程安全,不存在竞争问题
- 栈大小默认较小(如 -Xss512k),不能跨线程访问
堆内存是整个 JVM 进程内所有线程共享的全局空间:
- 一个对象被创建后,任何持有其引用的线程都能读写
- 多线程并发访问同一对象时,必须考虑可见性、原子性、有序性
- 堆空间大(如 -Xmx2g),是内存泄漏和 OutOfMemoryError 的主要发生地
常见错误和对应表现
StackOverflowError:通常因递归过深、方法嵌套太深,或单个方法定义了过多局部变量,撑爆了栈空间。
OutOfMemoryError: Java heap space:对象创建太多、单个对象过大(如加载几百 MB 文件到 byte[])、或 GC 回收不及时导致堆耗尽。
写代码时,问自己一句:这个变量是“临时用一下”,还是“要活得久、被别人用”?答案就自然落在栈或堆里了。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











