垃圾回收算法G1gc和Zgc的区别及重要特性总结
本文由 AI 生成,内容仅供参考,请仔细甄别。
核心设计目标对比:
- G1 GC (Garbage-First): 主要目标是在可控的停顿时间(通常几百毫秒以内)下实现高吞吐量。它针对大内存(几十GB)进行了优化,但并非追求极低停顿。
- ZGC (Z Garbage Collector): 核心设计目标是将停顿时间控制在极低水平(通常10ms以下,甚至亚毫秒级),且停顿时间几乎不随堆大小(可达TB级)增长而增加。吞吐量是其次要目标(虽然也在不断提升)。
核心GC过程详解 (关键区别所在):
-
标记阶段 (Marking):
- G1 GC:
- 并发标记 (Concurrent Marking): 这是G1的核心优势之一。与应用线程并发运行,遍历对象图找出存活对象。过程分多个子阶段:
- 初始标记 (Initial Mark): STW。标记从GC Roots直接可达的对象。这个阶段通常很快,因为它只标记根的直接引用。
- 根区域扫描 (Root Region Scanning): 并发。扫描Survivor区(属于根区域)中对象指向老年代的引用。必须在下一个Young GC开始前完成。
- 并发标记 (Concurrent Marking): 并发。遍历整个堆的对象图,找出所有存活对象。可能与应用线程并发修改对象图产生浮动垃圾。
- 最终标记 (Remark): STW。处理在并发标记期间应用线程产生的引用变化(使用SATB - Snapshot-At-The-Beginning算法)。需要短暂暂停应用线程来确保标记结果的准确性。
- 清理 (Cleanup): STW (部分并发)。计算各个Region的存活对象信息,对Region进行排序(根据可回收空间和回收成本)。识别出完全空闲的Region进行回收。这一步本身有短暂的STW来准备回收,实际回收部分Region可以并发。
- 并发标记 (Concurrent Marking): 这是G1的核心优势之一。与应用线程并发运行,遍历对象图找出存活对象。过程分多个子阶段:
- ZGC:
- 并发标记 (Concurrent Marking): 完全并发。ZGC的标记阶段几乎完全与应用线程并发执行。关键实现机制:
- 染色指针 (Colored Pointers): 这是ZGC的基石。它在64位对象指针(实际只用了42-48位寻址)的高位元数据位中存储标记信息(Marked0, Marked1, Remapped)和对象状态(Finalizable等)。无需修改对象头。
- 读屏障 (Load Barrier): 这是实现全并发标记和转移的核心。 当应用线程从堆内存加载引用时,这个屏障代码会被触发。它的主要作用是检查指针的元数据位:
- 如果发现指针指向的对象尚未被标记(或需要重映射),屏障会确保在应用代码使用该引用前,完成必要的标记或重映射操作(可能由该线程自己执行,或由GC线程完成)。
- 这保证了应用线程看到的内存视图始终是“正确”的(要么指向旧地址但标记了,要么指向新地址),从而允许GC线程完全并发地进行标记和转移。
- ZGC标记过程: 没有传统意义上的STW初始标记和最终标记阶段。整个标记过程通过读屏障与应用线程高度协作并发完成,避免了长时间的STW暂停。ZGC的标记阶段通常包括多个循环(Mark Start, Mark End),但都是在并发状态下完成。
- 并发标记 (Concurrent Marking): 完全并发。ZGC的标记阶段几乎完全与应用线程并发执行。关键实现机制:
- G1 GC:
-
转移/疏散阶段 (Evacuation/Relocation):
- G1 GC:
- 疏散暂停 (Evacuation Pause): STW。 这是G1产生主要停顿时间的阶段。G1根据之前的标记和Region排序结果,选择一组Region(称为Collection Set, CSet)进行回收。CSet通常包含所有年轻代Region和部分预测回收收益高的老年代Region。
- 过程: 在STW期间,将CSet中存活的对象复制(疏散)到新的、空闲的Region中(目标可能是Survivor区或老年代Region)。同时更新所有指向这些被移动对象的引用。
- 机制: 使用写屏障 (Write Barrier) 记录在并发标记阶段应用线程修改对象引用(称为SATB日志),确保在最终标记阶段能正确处理这些变更。转移阶段本身需要STW来原子性地完成对象的移动和引用的更新。
- ZGC:
- 并发转移 (Concurrent Relocation): 完全并发。 这是ZGC最革命性的特性之一。它不需要STW来移动对象。
- 过程:
- ZGC选择一组需要回收(通常是垃圾比例很高)的Region作为重分配集 (Relocation Set)。
- 读屏障再次成为关键: 当应用线程尝试访问位于重分配集中的对象时:
- 读屏障会检查该对象是否已被转移。如果没有,则由当前应用线程(或GC线程)立即执行该对象的转移(复制到新的Region),并原子性地更新该引用指针(通过CAS操作)指向新地址,并设置指针的
Remapped
状态。 - 同时,在旧对象位置留下一个转发指针 (Forwarding Pointer),指向新位置。
- 读屏障会检查该对象是否已被转移。如果没有,则由当前应用线程(或GC线程)立即执行该对象的转移(复制到新的Region),并原子性地更新该引用指针(通过CAS操作)指向新地址,并设置指针的
- 其他线程访问同一对象时,读屏障要么看到
Remapped
状态直接使用新地址,要么通过转发指针找到新地址并尝试更新自己的引用。
- 转移后处理 (Remapping): 在下一个标记周期中,ZGC会并发地遍历所有引用(特别是那些在转移期间没有被应用线程访问到的对象引用),利用留下的转发指针,将这些引用更新到对象的新地址(即进行重映射)。读屏障在这个过程中也会协助修复应用线程新加载的引用。
- G1 GC:
重要特性总结:
-
G1 GC:
- 分代收集: 逻辑上仍然区分年轻代(Eden, Survivor)和老年代,但物理上是大小相等的Region。
- Region分区: 堆被划分为多个固定大小(默认约1MB-32MB)的Region,回收以Region为单位。
- 增量回收与混合收集: 不是一次回收整个堆,而是增量地进行。混合收集既回收年轻代Region,也回收选定的老年代Region。
- 可预测停顿模型: 通过设置
-XX:MaxGCPauseMillis
(默认200ms)目标,G1尽力(但不保证)控制每次GC停顿时间在该目标内。它根据历史数据和Region信息预测选择回收收益最大的Region组成CSet。 - Remembered Sets (RSet): 每个Region都有一个RSet,记录其他Region指向本Region内对象的引用。用于避免在Young GC时扫描整个老年代。维护RSet是G1的重要开销来源之一。
- SATB算法: 处理并发标记期间引用变化的算法,以标记开始时的对象图为快照。
- STW阶段: 存在明确的STW阶段:初始标记、最终标记、疏散暂停(主要停顿)、清理(部分STW)。
-
ZGC:
- 亚毫秒级停顿目标: 首要设计目标,停顿时间通常在10ms以下,且与堆大小无关(TB级堆也能保持)。
- 全并发操作: 标记、转移(重定位)、重映射等关键阶段几乎都是并发执行的,极大地减少了STW。
- 染色指针 (Colored Pointers): 核心创新,将GC元数据存储在指针本身,无需对象头修改,减少了内存占用和访问开销。同时实现了高效的屏障检查和并发处理。
- 读屏障 (Load Barrier): 实现并发标记和并发转移的基石。代价是每次从堆加载引用都有额外检查(但JVM高度优化了屏障代码)。
- NUMA感知: 对非统一内存访问架构有良好支持,优先在本地NUMA节点分配内存,提升性能。
- 不分代 (Single Generation - 目前): 当前版本的ZGC(截至JDK 21)没有物理或逻辑上的分代。它统一管理整个堆。这是其追求极致低延迟的一个设计选择(避免分代间引用跟踪的复杂性和潜在停顿)。未来计划引入分代式ZGC (Generational ZGC - 预览中)。
- 基于Region: 堆也划分为Region(称为ZPages,大小更灵活:2MB, 32MB等),但管理方式与G1不同。
- 极少的STW: STW阶段非常短暂,主要用于启动GC周期(如根扫描准备)和极少数必要的全局同步点(如JVM安全点),时间通常固定且极短(与堆大小无关)。
如何选择?
-
选择 G1 GC:
- 堆大小在几十GB到一两百GB级别。
- 可以容忍几百毫秒的GC停顿(通常要求 < 200ms)。
- 追求较高的整体吞吐量。
- 运行在JDK 8, 11等较早版本(ZGC支持可能受限或不够成熟)。
- 应用对中等延迟敏感,但非极端要求。
-
选择 ZGC:
- 堆非常大(数百GB甚至TB级)。
- 对停顿时间极度敏感,要求暂停在10ms甚至亚毫秒级(如金融交易、实时系统、大型交互式应用)。
- 应用需要极低的服务延迟保证。
- 运行在较新的JDK版本(JDK 15+,生产推荐JDK 17 LTS+)。
- 愿意接受为读屏障带来的轻微吞吐量开销(与低停顿的收益相比通常是值得的)。
总结关键区别表:
特性 | G1 GC | ZGC |
---|---|---|
主要目标 | 可控停顿(几百ms)下的高吞吐量 | 极低停顿(<10ms),堆大小无关 |
堆大小适应性 | 大堆(几十GB) | 超大堆(TB级) |
分代 | 是 (逻辑分代, 物理Region) | 否 (目前统一管理,分代ZGC在预览中) |
标记阶段 | 并发标记 + STW子阶段(初始/最终标记) | 完全并发标记 (依赖读屏障) |
转移/疏散阶段 | STW (Evacuation Pause) | 完全并发转移 (核心革命, 依赖读屏障) |
关键创新 | Region, RSet, SATB, 预测模型 | 染色指针, 读屏障 |
核心STW来源 | 初始标记, 最终标记, 疏散暂停 | 极短暂的根处理/安全点 |
停顿时间预测性 | 尽力控制(MaxGCPauseMillis) | 高度可预测且极低 |
停顿与堆大小关系 | 随堆增大可能增加 | 基本无关 |
内存开销 | 中等 (RSet, Card Table) | 较低 (元数据在指针) |
吞吐量 | 高 | 中等至高 (持续优化中,略低于G1) |
JDK支持 | JDK 7u4+ (生产: JDK 8u20+, JDK9默认) | JDK 11+ (实验), JDK15+ (生产) |