ZGC低延迟垃圾回收器
约 1877 字大约 6 分钟
javazgcgc
2025-03-12
概述
ZGC(Z Garbage Collector)是一款以超低延迟为设计目标的垃圾回收器,从 JDK 11 开始作为实验特性引入,JDK 15 正式发布。ZGC 的最大亮点是停顿时间不超过亚毫秒级别,且停顿时间不随堆大小或存活对象数量增加而增长。
设计目标
| 目标 | ZGC表现 |
|---|---|
| 最大停顿时间 | < 1ms(亚毫秒级) |
| 停顿时间是否随堆增大 | 否(恒定) |
| 支持堆大小 | 8MB ~ 16TB |
| 吞吐量损失 | 相比G1通常 < 15% |
核心技术:着色指针(Colored Pointers)
ZGC最创新的设计是在对象指针中嵌入元数据,利用64位指针的高位存储GC状态信息。
|----------------------------------------------------------------------|
| 63-48 (unused) | 47 | 46 | 45 | 44 | 43-0 (object address, 44位) |
|----------------------------------------------------------------------|
| | | |
| | | +-- Remapped(重映射完成)
| | +------ Marked1(标记位1)
| +----------- Marked0(标记位0)
+---------------- Finalizable(可终结)为什么44位地址够用? 44位可以寻址 2^44 = 16TB,对于绝大多数应用足够。ZGC通过多重映射(multi-mapping)将同一物理内存映射到三个虚拟地址空间,不同着色状态的指针指向不同的虚拟地址但同一物理位置。
核心技术:读屏障(Load Barrier)
与G1/CMS使用写屏障不同,ZGC使用读屏障(Load Barrier)。每次从堆中加载对象引用时,都会检查着色指针的状态位。
// 读屏障伪代码
Object loadBarrier(Object* ref) {
Object obj = *ref;
if (isBadColor(obj)) {
// 指针颜色不对(指向旧地址),需要修正
obj = slowPath(ref, obj); // 重映射/标记/重定位
}
return obj;
}关键优势:读屏障使得GC可以在不暂停应用线程的情况下移动对象 — 应用线程读到旧引用时会自动修正。
GC工作阶段
详细阶段说明
| 阶段 | 并发/STW | 说明 |
|---|---|---|
| Pause Mark Start | STW | 标记GC Roots直接引用的对象(极短暂) |
| Concurrent Mark | 并发 | 遍历对象图,标记所有存活对象 |
| Pause Mark End | STW | 处理标记残留(极短暂) |
| Concurrent Process | 并发 | 处理软/弱/虚/终结器引用 |
| Concurrent Reset Reloc Set | 并发 | 选择需要回收的Region |
| Pause Relocate Start | STW | 标记GC Roots中需要重定位的引用(极短暂) |
| Concurrent Relocate | 并发 | 将存活对象复制到新Region,原Region回收 |
三次STW停顿的时间都与堆大小和存活对象数量无关,仅与GC Roots数量相关,通常在亚毫秒级别。
Region类型
| Region类型 | 大小 | 对象大小范围 | 说明 |
|---|---|---|---|
| Small | 2MB | < 256KB | 最常见 |
| Medium | 32MB | 256KB ~ 4MB | 中等对象 |
| Large | N * 2MB | >= 4MB | 每个Large Region只存一个对象 |
分代ZGC(Generational ZGC, JDK 21+)
JDK 21 引入分代ZGC,将堆分为年轻代和老年代,大幅提升吞吐量。
分代ZGC的优势:
- 年轻代GC更频繁但更快(大部分对象朝生夕灭)
- 老年代GC更少触发
- 相比非分代ZGC,吞吐量提升约10%~20%
- 内存开销更低(减少了浮动垃圾)
# JDK 21+ 启用分代ZGC(JDK 23+ 默认就是分代ZGC)
-XX:+UseZGC -XX:+ZGenerational
# JDK 23+ 分代ZGC是唯一选项
-XX:+UseZGC配置参数
# 基本配置
-XX:+UseZGC # 启用ZGC
-Xms4g -Xmx4g # 建议Xms=Xmx,避免堆大小波动
-XX:+ZGenerational # 启用分代ZGC(JDK 21+)
# 调优参数
-XX:SoftMaxHeapSize=3g # 软性堆上限(GC尽量在此范围内)
-XX:ConcGCThreads=4 # 并发GC线程数(默认CPU核数/8)
-XX:ZAllocationSpikeTolerance=5 # 分配突增容忍度
-XX:ZCollectionInterval=0 # GC间隔(0=按需触发)
-XX:ZFragmentationLimit=25 # 碎片率阈值
# 诊断
-Xlog:gc*:file=gc.log:time # GC日志
-XX:+ZStatisticsForceTrace # 强制输出统计信息ZGC vs G1 vs Shenandoah
| 特性 | G1 | ZGC | Shenandoah |
|---|---|---|---|
| 停顿时间 | 几十~几百ms | < 1ms | < 10ms |
| 停顿是否与堆成正比 | 是 | 否 | 否 |
| 支持堆大小 | GB~几百GB | MB~16TB | GB~几百GB |
| 核心技术 | SATB+分区回收 | 着色指针+读屏障 | Brooks指针+读屏障 |
| 吞吐量 | 高 | 中高 | 中高 |
| 分代 | 是 | 是(JDK 21+) | 否 |
| 默认收集器 | JDK 9~20 | — | — |
| 最低JDK | JDK 7 | JDK 11 | JDK 12 |
适用场景
ZGC最适合以下场景:
典型适用场景:
- 金融交易系统(低延迟)
- 实时推荐系统
- 大内存数据处理(几百GB堆)
- 微服务对响应时间敏感的场景
常见FAQ
Q: ZGC的吞吐量比G1差吗?
A: 非分代ZGC由于缺少分代优化,吞吐量确实略低于G1(约5~15%)。但分代ZGC(JDK 21+)已经大幅缩小差距,在某些场景下甚至超过G1。
Q: ZGC支持压缩指针(Compressed Oops)吗?
A: JDK 15之前不支持,因为着色指针占用了高位。JDK 15+ 通过优化支持了压缩指针,但仅在堆 <= 4GB 时启用。分代ZGC进一步改善了这一限制。
Q: ZGC会产生Full GC吗?
A: ZGC没有传统意义上的Full GC。如果内存分配速度超过GC回收速度,ZGC会增大GC频率。极端情况下(完全分配不出内存),会抛出OutOfMemoryError。
Q: 生产环境推荐使用ZGC吗?
A: JDK 17+(LTS)的ZGC已经非常成熟,推荐在对延迟敏感的生产环境使用。JDK 21+的分代ZGC更是显著提升了吞吐量,适合更广泛的场景。
总结
ZGC通过着色指针和读屏障两大核心技术,实现了几乎不停顿的垃圾回收。它将STW停顿控制在亚毫秒级别,且不随堆大小增长。JDK 21引入的分代ZGC进一步解决了吞吐量短板,使ZGC成为对延迟敏感的Java应用的理想选择。随着JDK版本的演进,ZGC有望在更多场景中替代G1成为默认选择。
贡献者
更新日志
9f6c2-feat: organize wiki content and refresh site setup于