ZGC与低延迟GC
ZGC 是 HotSpot JVM 中面向低延迟和大堆场景的垃圾回收器。它的目标是把 GC 停顿控制在很低水平,让应用在大内存下也能保持稳定响应。
# 1. ZGC 适合什么场景
ZGC 适合:
- 大堆服务。
- 对 P99、P999 延迟敏感的服务。
- 实时交易、网关、在线推荐等低延迟场景。
- 希望减少长时间 STW 的应用。
不一定适合:
- 小内存工具。
- 对吞吐极致敏感但延迟不敏感的批处理。
- 团队缺少低延迟 GC 观测经验的系统。
# 2. ZGC 的核心思想
ZGC 尽量把 GC 工作并发化。
大部分阶段中,GC 线程和应用线程可以同时运行。STW 阶段只做很少工作,因此停顿时间可以很短。
它使用的关键技术包括:
- 并发标记。
- 并发转移。
- 染色指针。
- 读屏障。
# 3. 染色指针
染色指针把一些元信息编码进对象引用中,用于表示对象状态。
这样 ZGC 可以在对象移动和引用修正过程中保持并发执行能力。
理解时可以把它看作:引用不只是地址,还携带了一些 GC 状态信息。
# 4. 读屏障
读屏障是在读取对象引用时插入的一小段逻辑。
它帮助 ZGC 在应用线程访问对象时,处理并发标记、转移和引用修正相关工作。
这也是 ZGC 能够大幅减少 STW 的关键代价之一:用更多运行时屏障换取更低停顿。
# 5. 分代 ZGC
早期 ZGC 不分代。JDK 21 引入分代 ZGC,使 ZGC 能更好利用“多数对象朝生夕死”的规律。
分代后,ZGC 可以更高效地处理年轻对象和老对象,减少不必要的全堆压力。
# 6. 使用方式
启用 ZGC:
-XX:+UseZGC
常见配置还包括堆大小:
-Xms8g -Xmx8g -XX:+UseZGC
低延迟场景下,堆大小、分配速率、CPU 资源和业务延迟目标要一起评估。
# 7. ZGC 不是无成本
ZGC 的低停顿来自复杂机制,也有代价:
- 可能增加 CPU 开销。
- 对运行时版本要求较高。
- 需要关注并发回收是否追得上分配速度。
- 需要更成熟的监控和压测。
如果应用堆不大、延迟要求普通,G1 可能已经足够。
# 8. 阶段小结
ZGC 是低延迟 GC 的重要选择,特别适合大堆和延迟敏感系统。选用 ZGC 的前提是明确业务确实受 GC 停顿影响,并通过压测和监控确认收益,而不是因为它名字更先进就直接上生产。
# 9. ZGC 停顿为什么短
ZGC 把大部分工作并发化:
传统收集器可能:
STW 标记 -> STW 移动 -> STW 修引用
ZGC 尽量:
短 STW 初始阶段
并发标记
短 STW 同步点
并发转移
并发引用修正
停顿短不是因为 GC 工作消失了,而是更多工作和应用线程并发执行了。
# 10. 染色指针和读屏障的直观理解
可以把对象引用理解成带“颜色标签”的地址:
引用 = 地址 + GC 状态位
应用线程读取引用时,读屏障会检查引用状态。如果发现对象已经移动或引用需要修正,就帮助完成修正。
读取对象引用
│
▼
读屏障检查颜色/状态
│
├─ 正常 -> 直接访问
└─ 需要修正 -> 更新引用后访问
这让 ZGC 可以在对象移动时仍保持应用线程运行。
# 11. 分代 ZGC 的意义
早期 ZGC 不分代,对年轻对象和老对象没有像传统分代那样区别处理。JDK 21 引入分代 ZGC 后,可以更好利用对象生命周期规律:
- 年轻对象快速回收。
- 老对象降低扫描和移动频率。
- 减少全堆压力。
- 更适合高分配速率应用。
# 12. 选用 ZGC 前要问的问题
| 问题 | 为什么重要 |
|---|---|
| P99/P999 是否真的受 GC 影响 | 避免错误归因 |
| 堆有多大 | ZGC 更适合大堆低延迟 |
| CPU 是否充足 | 并发 GC 需要 CPU |
| JDK 版本是否合适 | 不同版本 ZGC 能力差异明显 |
| 是否有监控 JFR/GC 日志 | 低延迟系统更依赖观测 |
# 13. Tips 快问快答
Q:ZGC 没有 STW 吗?
A:有,但目标是把 STW 控制得很短。不是完全没有停顿。
Q:ZGC 会不会降低吞吐?
A:可能。低停顿通常需要更多并发工作和屏障成本,要通过压测判断。
Q:小堆应用需要 ZGC 吗?
A:不一定。普通小堆服务使用 G1 可能更简单、更稳妥。
Q:分代 ZGC 解决了什么问题?
A:更好利用多数对象短命的规律,提高回收效率,降低不必要的全堆处理压力。