中 困难
ZGC特性#
一句话答案#
ZGC 是低延迟 GC,停顿不随堆增大而增加(最初目标 <10ms,现在官方口径是不超过 1ms),核心技术:着色指针 + 读屏障,JDK15 生产就绪,JDK 21 引入分代 ZGC、JDK 24 起只剩分代模式。
核心要点
ZGC(Z Garbage Collector):
- JDK 11 实验性引入,JDK 15 正式支持
- 目标:停顿时间与堆大小无关(无论 8GB 还是 8TB);JEP 333 最初目标是 < 10ms,现在官方 wiki 口径是停顿不超过 1ms
- 版本演进:JDK 21 引入分代 ZGC(JEP 439,需
-XX:+ZGenerational)→ JDK 23 分代成为默认(JEP 474)→ JDK 24 移除非分代模式(JEP 490),-XX:+UseZGC即分代 ZGC
核心技术1:染色指针(Colored Pointers)
普通指针:[0...0][地址](64位,低46位存地址)
ZGC指针: [标记位][地址](利用64位指针的高位存元数据)
标记位(4位):
Finalizable | Remapped | Marked1 | Marked0
是否进入finalize | 是否重映射 | GC标记位 | GC标记位plaintext- 直接在指针上存储引用的 GC 状态,读屏障只看指针就能判断要不要处理(存活标记仍另有每个 Page 的 live map 位图)
- 好处:GC 状态读取不需要访问对象头,更快
- 注:上面 4 个标记位是非分代 ZGC 的布局;分代 ZGC(JDK 21+)重新设计了指针染色,元数据位更多,并记录新生代/老年代标记和记忆集信息
核心技术2:读屏障(Load Barrier)
- 每次读取引用时(Load),JVM 插入一小段检查代码
- 检查指针的染色位,如果发现对象已被移动,立刻更新引用(Self-Healing)
- 好处:GC 可以在并发阶段移动对象,不需要 STW 全量更新引用
- 代价:每次读引用有额外开销,吞吐量有损耗(JEP 333 的设计目标是相比 G1 吞吐下降不超过 15%)
ZGC 的工作流程(几乎全并发):
(非分代 ZGC 的一个周期;分代 ZGC 对新生代/老年代各跑类似的周期)
初始标记 → STW(极短,< 1ms)
并发标记 → 与用户线程并发
再标记 → STW(极短)
并发转移准备 → 并发
初始转移 → STW(极短)
并发转移 → 与用户线程并发(移动对象,读屏障保证安全)plaintextZGC 适用场景: 超大堆(数十GB~TB),对延迟极度敏感(金融交易、实时系统)
面试回答(2分钟版)
ZGC是JDK11引入、JDK15正式生产就绪的低延迟垃圾收集器,它最初的目标是将GC停顿控制在10毫秒以内,现在官方口径已经是不超过1毫秒,而且这个停顿时间不随堆大小增长——无论堆是8GB还是8TB,停顿都一样短。它靠两个核心技术实现:第一是染色指针,利用64位指针的高位存储GC元数据信息,这样读取GC状态不需要访问对象头,效率更高;第二是读屏障,每次加载引用时插入一小段检查代码,如果发现对象已经被GC移动了,就立刻自愈更新引用指向新地址。这样GC在并发转移对象时不需要STW去全量更新引用。ZGC的工作流程几乎全并发,只有初始标记、再标记和初始转移三个极短的STW阶段,每个不到1毫秒。代价是读屏障带来一定的吞吐开销(设计目标是相比G1不超过15%)。另外JDK21引入了分代ZGC,JDK24起非分代模式已移除,现在用ZGC就是分代ZGC。适用场景是超大堆加低延迟要求的系统,比如金融交易、实时计算;小堆场景G1可能更合适。
追问与易错
追问方向:
- “ZGC 怎么做到停顿这么短(官方口径不超过 1ms)?”→ 几乎全并发设计,只有初始标记/再标记/初始转移三个极短 STW 阶段(各 < 1ms);并发转移靠染色指针+读屏障实现对象移动时无需 STW 全量更新引用
- “ZGC 和 G1 怎么选?”→ 堆 < 8GB 且延迟要求不极端用 G1 足够;超大堆(几十 GB~TB)或对延迟极度敏感(< 10ms)选 ZGC;ZGC 有一定吞吐开销(设计目标相比 G1 不超过 15%)需权衡;JDK 21+ 推荐分代 ZGC
- “ZGC 有什么缺点?”→ 读屏障带来吞吐损耗、不支持 32 位系统、依赖 64 位指针(不支持指针压缩 CompressedOops)、JDK 版本要求高(JDK15+ 生产就绪,分代 ZGC 需 JDK 21+)
易错点:
- ❌ ZGC 完全没有 STW——初始标记仍有极短 STW
- ❌ 所有场景都用 ZGC——小堆 G1 可能更合适