引言
Java 是垃圾回收 (GC) 技术的集大成者。从最早的 Serial GC 到如今的 ZGC,JVM 的 GC 演进史就是一部 GC 技术的发展史。本文将深入探讨 Java GC 的核心算法与实现细节。
1. 分代收集理论 (Generational Collection)
绝大多数 Java 对象都是“朝生夕死”的。基于这个观察,JVM 将堆内存划分为新生代 (Young Generation) 和 老年代 (Old Generation)。
1.1 跨代引用与卡表 (Card Table)
分代收集的一个核心问题是:如何处理老年代对象指向新生代对象的引用? 如果在 Minor GC 时扫描整个老年代来查找这些引用,性能将无法接受。JVM 引入了 卡表 (Card Table) 技术。
- 原理:将老年代划分为 512 字节的块 (Card)。
- 写屏障 (Write Barrier):当老年代对象引用发生变化时,通过写屏障将对应的 Card 标记为 “Dirty”。
- 扫描:Minor GC 只需要扫描 “Dirty” 的 Card,即可找到所有的跨代引用。
写屏障伪代码 (Write Barrier Logic):
void oop_field_store(oop* field, oop new_value) {
*field = new_value; // 1. 更新引用
if (is_old_gen(field) && is_young_gen(new_value)) {
// 2. 写后屏障 (Post-Write Barrier)
// 计算卡表索引
size_t card_index = (uintptr_t)field >> CARD_SHIFT;
// 标记为脏
byte_map_base[card_index] = 0;
}
}2. CMS (Concurrent Mark Sweep)
CMS 是第一款真正意义上的并发收集器,目标是获取最短回收停顿时间。它基于标记-清除算法。
2.1 算法步骤详解
- 初始标记 (Initial Mark) [STW]
- 标记 GC Roots 能直接关联到的对象。
- 速度很快。
- 并发标记 (Concurrent Mark)
- 从 GC Roots 开始遍历整个对象图。
- 与用户线程并发运行。
- 重新标记 (Remark) [STW]
- 修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。
- 使用 增量更新 (Incremental Update) 算法解决并发一致性问题。
- 并发清除 (Concurrent Sweep)
- 清除未标记的对象。
2.2 增量更新 (Incremental Update)
CMS 使用增量更新来解决并发标记时的漏标问题。 * 核心思想:当黑色对象插入新的指向白色对象的引用关系时,就将这个新插入的引用记录下来,等并发扫描结束之后,再将这些记录过的引用关系中的黑色对象为根,重新扫描一次。
3. G1 (Garbage First)
G1 开创了面向局部收集的设计思路和基于 Region 的内存布局形式。
3.1 Region 与 Remembered Set (RSet)
G1 将堆划分为多个 Region。每个 Region 都有一个 RSet,记录了”谁引用了我”。 * Point In:RSet 记录的是别的 Region 指向自己的指针。 * 价值:回收某个 Region 时,不需要扫描整个堆,只需要扫描该 Region 的 RSet 即可确定存活对象。
3.2 SATB (Snapshot At The Beginning)
G1 使用 SATB 算法来维持并发标记的正确性,不同于 CMS 的增量更新。
- 核心思想:在 GC 开始时,逻辑上对堆进行一次快照。
- 写前屏障 (Pre-Write Barrier):当引用关系改变时,将旧的引用记录下来。
SATB 写屏障伪代码:
void oop_field_store(oop* field, oop new_value) {
oop old_value = *field;
// 1. 写前屏障:记录旧值
if (gc_is_marking && old_value != null) {
enqueue(old_value); // 加入 SATB 队列,视为存活
}
*field = new_value; // 2. 更新引用
// 3. 写后屏障 (用于维护 RSet)
if (cross_region_ref(field, new_value)) {
update_remembered_set(field, new_value);
}
}SATB vs 增量更新: * CMS (增量更新): 关注引用的增加。 * G1 (SATB): 关注引用的删除 (保存旧引用,保证快照一致性)。
4. ZGC (Z Garbage Collector)
ZGC 是基于页 (Page) 的、不分代的(JDK 21之前)、低延迟垃圾收集器。
4.1 染色指针 (Colored Pointers)
ZGC 将对象的状态信息直接存储在指针上,而不是对象头中。64位指针被划分为: * 0-41 bit: 对象地址 (4TB 支持) * 42-45 bit: 标志位 (Finalizable, Remapped, Marked1, Marked0)
4.2 读屏障 (Load Barrier)
ZGC 不使用写屏障来维护对象引用,而是使用读屏障。当程序读取一个对象引用时,ZGC 会检查指针颜色。
读屏障伪代码:
oop load_barrier(oop* ptr) {
oop ref = *ptr;
// 检查指针颜色是否正确 (Bad Color Check)
if (has_bad_mask(ref)) {
// 慢路径:修正指针 (Remap/Mark)
ref = slow_path_fix(ref);
// 更新内存中的指针,下次访问就是快路径
*ptr = ref;
}
return ref;
}- 自愈 (Self-Healing): 一旦读屏障修正了指针,后续访问直接走快路径,性能损耗极低。
总结
| 特性 | CMS | G1 | ZGC |
|---|---|---|---|
| 算法 | 标记-清除 | 标记-整理 + 复制 | 标记-整理 (并发) |
| 并发保障 | 增量更新 (写后屏障) | SATB (写前屏障) | 读屏障 |
| 内存布局 | 分代 (物理连续) | Region (物理不连续) | Page (动态大小) |
| STW 目标 | 低 | 可控 (MaxGCPauseMillis) | < 1ms (JDK 21+) |