本文将从对象完整生命周期的视角,系统性地阐述JVM内存管理和垃圾回收机制。你将看到对象如何出生、如何存活、如何晋升,以及最终如何被回收的完整过程。
核心叙事线:一个对象的"人生旅程"
- 出生:在堆内存中分配(Eden区)
- 成长:在Survivor区中经历多次GC考验
- 成熟:晋升到老年代安享晚年
- 终结:被GC回收,生命结束
- 底层支撑:内存模型如何保证这个过程的线程安全
第一部分:对象的诞生与内存分配
1.1 内存的舞台:运行时数据区全景
在对象出生之前,我们先看看JVM为它准备了什么样的舞台。

1.2 对象的创建过程(逐步分解)
Object obj = new Object();
这行简单代码背后,JVM执行了复杂的操作:
-
类加载检查:检查new指令的参数是否能在常量池中定位到类的符号引用,并检查类是否已被加载、解析和初始化。
-
内存分配:在堆中为新生对象分配内存。分配方式有两种:
- 指针碰撞:内存规整时,移动指针划分内存
- 空闲列表:内存不规整时,从空闲列表中找到足够大的空间
-
内存空间初始化:将分配到的内存空间都初始化为零值(不包括对象头)
-
设置对象头:存储对象的元数据(哈希码、GC分代年龄、锁状态等)
-
执行
<init>****方法:按照程序员的意愿进行初始化

其中Mark Word 在32位虚拟机中结构如下:

在64位虚拟机中结构如下:

1.3 内存分配策略
- 优先在Eden区分配:大多数新对象在Eden区分配
- 大对象直接进入老年代:避免在Eden区和Survivor区之间大量复制
- 长期存活的对象进入老年代:对象年龄计数器达到阈值(默认15)时晋升
- 动态年龄判定:Survivor区中相同年龄所有对象大小超过Survivor空间一半时,年龄≥该年龄的对象直接晋升
第二部分:对象的存活与GC算法
2.1 判断对象存活的算法
引用计数法(Python采用):
- 优点:实现简单,判断高效
- 缺点:无法解决循环引用问题

可达性分析算法(Java采用):
- 从GC Roots对象作为起点,向下搜索,走过的路径称为"引用链"
- 如果一个对象到GC Roots没有任何引用链相连,则判定为可回收
GC Roots包括:
- 虚拟机栈中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI引用的对象
- Java虚拟机内部的引用(基本类型对应的Class对象、系统类加载器等)
- 被同步锁持有的对象

2.2 引用类型:强、软、弱、虚
- 强引用:普通的
Object obj = new Object(),永远不会被GC - 软引用:内存不足时会被回收,适合做缓存
- 弱引用:下次GC时就会被回收
- 虚引用:无法通过虚引用获取对象,主要用于跟踪对象被回收的状态
1. 强引用
-
创建语句:就是普通的对象赋值。
Object obj = new Object(); // obj就是一个强引用 String str = "Hello"; // str也是一个强引用 -
核心特性:
- 只要强引用关系存在,垃圾收集器就永远不会回收掉被引用的对象。
- 当内存不足时,JVM 会抛出
OutOfMemoryError错误,也不会通过回收强引用的对象来释放内存。
-
回收时机:当对象没有任何强引用指向它时(例如将
obj设置为null,或者obj离开了作用域),它才变得可被回收。 -
典型使用场景:我们日常开发中 99% 的代码都在使用强引用。它是构成程序骨架的默认引用类型。
2. 软引用
-
创建语句:使用
java.lang.ref.SoftReference类。// 创建一个强引用的对象 Object strongRef = new Object(); // 用一个强引用对象来创建一个软引用 SoftReference<Object> softRef = new SoftReference<>(strongRef);// 通常也会配合引用队列(ReferenceQueue)使用 ReferenceQueue<Object> queue = new ReferenceQueue<>(); SoftReference<Object> softRefWithQueue = new SoftReference<>(strongRef, queue);// 取消强引用,此时只剩下softRef这个软引用 strongRef = null;// 需要时尝试获取对象 Object target = softRef.get(); // 如果对象未被回收,则target不为null if (target != null) {// 对象还存在,可以使用 } else {// 对象已被回收,需要重新创建 } -
核心特性:在系统内存不足时,垃圾收集器会回收掉只被软引用指向的对象。回收发生在 OOM 错误被抛出之前。
-
回收时机:内存不足时。
-
典型使用场景:非常适合实现内存敏感的缓存。
- 图片缓存:将大量图片数据放在软引用缓存中。当应用内存紧张时(例如在后台运行,系统需要内存),缓存会被自动清除,避免 OOM。当用户再次回到应用时,虽然缓存可能没了,但可以从磁盘或网络重新加载。
- 计算结果缓存:缓存一些计算成本高但非必需的结果。
3. 弱引用
-
创建语句:使用
java.lang.ref.WeakReference类。Object strongRef = new Object(); WeakReference<Object> weakRef = new WeakReference<>(strongRef);// 取消强引用 strongRef = null;// 强制执行GC(仅用于演示,生产代码中不要轻易调用) System.gc();// GC后,weakRef.get()有很大概率返回null if (weakRef.get() == null) {System.out.println("对象已被GC回收"); } -
核心特性:无论内存是否充足,只要发生了垃圾收集,并且对象只被弱引用指向,那么这个对象就会被回收。它的生命周期比软引用更短。
-
回收时机:下一次垃圾收集发生时。
-
典型使用场景:
WeakHashMap****的键:WeakHashMap的键是弱引用。当某个键对象除了在WeakHashMap中被弱引用外,没有其他强引用时,下次GC这个键值对就会被自动移除。常用于存储对象的元数据,当对象本身失效时,元数据自动清理。- 防止内存泄漏的辅助结构:例如,在某些监听器模式下,可以用弱引用来保存监听器,这样当主对象不再使用时,监听器不会因为被缓存而无法回收。但使用时要非常小心,因为监听器可能在任何时候被GC掉。
- ThreadLocal 中的
ThreadLocalMap****的键 也使用了弱引用来避免内存泄漏(但值仍然是强引用,所以正确使用后需要手动remove())。
4. 虚引用
-
创建语句:使用
java.lang.ref.PhantomReference类。必须和引用队列(ReferenceQueue)联合使用。Object strongRef = new Object(); ReferenceQueue<Object> queue = new ReferenceQueue<>(); PhantomReference<Object> phantomRef = new PhantomReference<>(strongRef, queue);// 取消强引用 strongRef = null;// 此时,phantomRef.get() 永远返回 null,无法通过它获取对象// 执行GC后,对象被回收,JVM会将虚引用对象phantomRef本身加入到队列queue中 System.gc();// 检查引用队列,如果有元素出队,说明被监控的对象被回收了 Reference<?> ref = queue.poll(); if (ref != null) {System.out.println("检测到对象被回收,可以进行后续清理工作");// 通常在这里执行一些堆外内存释放等收尾操作 } -
核心特性:
- 无法通过虚引用获取对象实例,即
get()方法总是返回null。 - 唯一作用是利用引用队列跟踪对象被垃圾回收的准确时刻。
- 虚引用本身比它所引用的对象更“坚强”,需要显式地将其从队列中取出后,它本身才会被GC。
- 无法通过虚引用获取对象实例,即
-
回收时机:对象被GC的最终阶段。可以认为一个对象设置了虚引用,就等于被“判了死刑”,但虚引用就像刑场外的记者,它的存在让你能准确知道“行刑”(对象被回收)这个事件发生了。
-
典型使用场景:
- 管理堆外内存(如 NIO 的
DirectByteBuffer): 这是最经典的用途。JVM 的堆内存由 GC 管理,但通过Unsafe或 NIO 分配的堆外内存 GC 管不了。我们可以在 Java 堆中创建一个很小的对象(如DirectByteBuffer)来代表一块很大的堆外内存,并为这个对象关联一个虚引用。当这个小的 Java 对象被 GC 回收时(意味着没有强引用再指向它),通过虚引用队列的通知,我们就可以知道此时应该去释放对应的堆外内存,从而避免堆外内存泄漏。
- 管理堆外内存(如 NIO 的
总结对比
| 引用类型 | 创建方式 | 垃圾回收时机 | 生存时间(强度) | 用途 |
|---|---|---|---|---|
| 强引用 | Object obj = new Object() |
永远不会 | 最强 | 程序默认状态,所有正常对象创建 |
| 软引用 | SoftReference softRef = new SoftReference(obj) |
内存不足时 | 较强 | 实现内存敏感缓存(如图片缓存) |
| 弱引用 | WeakReference weakRef = new WeakReference(obj) |
下一次GC时 | 较弱 | WeakHashMap、防止内存泄漏的辅助缓存 |
| 虚引用 | PhantomReference phantomRef = new PhantomReference(obj, queue) |
对象被回收的最终时刻 | 最弱(无法获取对象) | 跟踪对象被回收的事件,用于堆外内存释放等收尾工作 |
第三部分:垃圾回收算法与实现
3.1 基础回收算法
标记-清除算法:
- 过程:先标记所有需要回收的对象,然后统一回收
- 缺点:产生内存碎片,分配大对象时可能失败

- 图片出处:https://www.cnblogs.com/trunks2008/p/15341715.html
复制算法:
- 过程:将内存分为两块,每次使用一块,将存活对象复制到另一块
- 优点:没有碎片,实现简单
- 缺点:内存利用率只有50%

- 图片出处:https://www.cnblogs.com/trunks2008/p/15341715.html
标记-整理算法:
- 过程:标记存活对象,让所有存活对象向一端移动,然后清理边界外的内存
- 优点:没有碎片问题
- 缺点:移动对象成本高

- 图片出处:https://www.cnblogs.com/trunks2008/p/15341715.html
3.2 分代收集理论:现代GC的基石
基于弱分代假说和强分代假说,堆内存被划分为:
-
新生代:对象朝生夕死,回收频繁
- 采用复制算法
- 比例:Eden:Survivor:Survivor = 8:1:1
-
老年代:对象存活率高,回收不频繁
- 采用标记-清除或标记-整理算法
-
跨代引用问题:老年代对象引用新生代对象,需要额外处理

- 图片出处:https://www.cnblogs.com/trunks2008/p/15341715.html
3.3 分代GC完整流程

第四部分:现代垃圾回收器详解
4.1 回收器分类
| 分类 | 新生代回收器 | 老年代回收器 | 特点 |
|---|---|---|---|
| 串行 | Serial | Serial Old | 单线程,STW时间长 |
| 并行 | ParNew | Parallel Old | 多线程,吞吐量优先 |
| 并发 | - | CMS, G1, ZGC | 低延迟优先 |
4.2 回收器多维度对比
| 维度 | Serial | Parallel | CMS | G1 | ZGC | Shenandoah |
|---|---|---|---|---|---|---|
| 设计目标 | 单线程轻量级应用 | 吞吐量优先 | 低延迟(响应敏感) | 平衡型(吞吐+低延迟) | 超低延迟(亚毫秒级) | 超低延迟(与堆大小无关) |
| 停顿时间 | 高(STW) | 中高(STW) | 中低(部分STW) | 低(可预测) | 极低(<1ms) | 极低(<1ms) |
| 吞吐量 | 一般 | 高 | 中高 | 中高 | 中(读屏障开销) | 中(写屏障开销较高) |
| 是否并发 | 否 | 否(年轻代并行) | 是 | 是 | 是 | 是 |
| 是否压缩/整理 | 是(标记-复制) | 是(标记-复制) | 否(标记-清除) | 是(标记-整理) | 是(并发压缩) | 是(并发复制) |
| 是否支持大堆内存 | ✅(小堆) | ✅(中等堆) | ⚠️(碎片化限制) | ✅(大堆) | ✅(TB级) | ✅(TB级) |
| 是否商用可用 | ✅(过时) | ✅ | ⚠️(JDK 9+废弃) | ✅(JDK 9+默认) | ✅(JDK 11+生产可用) | ✅(OpenJDK/RedHat) |
| 是否产生浮动垃圾 | ❌ | ❌ | ✅(CMS问题) | ❌(STW筛选回收) | ❌(染色指针+STW) | ❌(Brooks指针+STW) |
| 核心技术 | STW单线程 | STW多线程 | 并发标记+清除 | 区域化+垃圾优先回收 | 染色指针+读屏障 | Brooks指针+双屏障 |
| 适用场景 | 小型应用/嵌入式 | 批处理/计算密集型 | 响应敏感型系统 | 大内存服务/平衡型 | 金融/实时交易系统 | 分布式/跨平台场景 |
| 版本支持 | JDK 1.3.1+ | JDK 1.4.2+ | JDK 1.4.1+(JDK 9废弃) | JDK 7u4+(JDK 9默认) | JDK 11+ | JDK 12+ |
4.2 重要回收器深度解析
CMS(Concurrent Mark-Sweep)回收器:
- 目标:最短回收停顿时间
- 过程:初始标记→并发标记→重新标记→并发清除
- 缺点:产生内存碎片,对CPU资源敏感
G1(Garbage-First)回收器:
- 革命性变化:将堆划分为多个Region,优先回收价值最大的Region
- 过程:初始标记→并发标记→最终标记→筛选回收
- 可预测的停顿时间模型
ZGC和Shenandoah:
- 目标:亚毫秒级停顿时间
- 关键技术:染色指针、读屏障
- 几乎在所有停顿时间上都优于G1

第五部分:内存模型与GC的协同工作
6.1 并发的基石:JMM保证GC的正确性
GC过程中,JMM的关键作用:
- 安全点:GC发生时,所有线程必须到达一个安全点才能暂停
- 记忆集:解决跨代引用问题,避免全堆扫描
- 写屏障:在对象引用写入时执行额外操作,维护记忆集
6.2 实战案例:为什么GC需要Stop-The-World
// 在GC过程中,如果没有STW,可能发生:
// 线程A:读取对象O的字段f
// GC线程:移动对象O到新位置
// 线程A:使用字段f(此时对象已移动,可能访问到错误内存)// JMM通过STW保证在GC过程中对象引用关系不会变化
6.3 内存屏障与GC的协同
- 读屏障:在读取引用前执行,用于并发标记(G1、ZGC)
- 写屏障:在写入引用后执行,用于维护记忆集
完整生命周期案例:一个Web请求对象的旅程
让我们通过一个具体案例,完整理解对象的一生:
@RestController
public class UserController {@GetMapping("/user/{id}")public User getUser(@PathVariable String id) {// 1. id字符串在栈上分配(可能栈分配优化)// 2. User对象在Eden区分配User user = userService.findById(id);// 3. 方法返回,user引用出栈,但User对象仍在堆中// 4. 如果请求频繁,Eden区满,触发Minor GC// 5. 如果user对象仍被外部引用(如缓存),在可达性分析中存活// 6. 经历多次Young GC后,晋升到老年代// 7. 最终缓存失效,对象不可达,被Full GC回收return user;}
}
