ZGC 与 Shenandoah 低延迟回收器
ZGC 与 Shenandoah 的目标一致:把 GC 停顿降到 10ms 以内,且停顿时间不随堆大小增长。它们颠覆了"GC 需要长时间 STW"的认知,适合超大堆、延迟敏感的场景。
为什么需要新回收器
G1 的停顿虽然可控,但仍有 STW 阶段(初始标记、最终标记、清理),且 Mixed GC 需要全局整理。当堆达到几十 GB 甚至上百 GB 时,G1 的停顿很难压到 10ms 以内。
ZGC 与 Shenandoah 的核心思路:除了极短的初始标记,其余阶段全部并发完成——包括并发标记、并发转移、并发重映射。GC 期间用户线程照常分配、访问对象。
ZGC:染色指针 + 读屏障
多代内存布局
ZGC 把堆划分为 Region,JDK 21 起支持分代 ZGC(分年轻代/老年代),回收粒度更细。Region 支持动态大小(2MB / 32MB / 512MB 三级),大对象独占大 Region。
染色指针(Colored Pointer)
ZGC 的核心创新:把对象指针的高 4 位用作标志位,对象引用本身携带 GC 状态,无需访问对象头即可判断对象状态。
64 位指针布局(未压缩时):
┌─┬─┬─┬─┬────────────────────────────────────────┐
│R│M│0│S│ 地址(42 位) │
└─┴─┴─┴─┴────────────────────────────────────────┘
R = Remapped(重映射标记位)
M = Marked(标记位)
S = Finalizable(终结对象标记位)- 标记位直接编码在指针中,判断"对象是否存活"无需读对象头
- 由于使用指针高位,禁用指针压缩(
-XX:+UseCompressedOops不生效),堆内存开销更大
读屏障(Load Barrier)
用户线程访问对象时,读屏障检查指针的标记位:
读屏障伪代码:
load(p):
如果 p 处于"待转移"状态(Remapped 位为 0):
定位 p 在新地址(通过转发表)
更新 p(重映射),返回新地址
否则直接返回 p读屏障让用户线程在访问对象的那一刻完成转移与重映射,且重映射只针对被访问的对象,实现了"转移与使用并发"。这也是 ZGC 停顿不随堆增长的秘密:转移工作被分摊到每一次对象访问中。
ZGC 回收流程
并发标记:标记所有存活对象(通过读屏障收集指针变化)
并发转移:将存活对象复制到新 Region,更新转发表
并发重映射:通过读屏障按需修复旧指针(推迟到访问时)三个阶段几乎全程并发,只有"并发标记"开始前的初始标记需要极短 STW。
Shenandoah:连接矩阵 + 转发指针
Shenandoah 同样全并发,但不使用染色指针,而是采用**转发指针(Forwarding Pointer)**方案:
- 对象转移后,在对象头中写入指向新地址的转发指针
- 用户线程访问对象时,通过读屏障检查并修正,自动跳到新地址
- 引用更新依赖**连接矩阵(Connection Matrix)**替代 ZGC 的转发表
| 对比项 | ZGC | Shenandoah |
|---|---|---|
| 指针技术 | 染色指针(指针高位编码状态) | 转发指针(对象头记录新址) |
| 屏障 | 读屏障 | 读屏障 |
| 指针压缩 | 不支持 | 支持 |
| 堆结构 | 多级 Region | 普通 Region |
| 默认回收器 | 是(JDK 15 起 JDK 21 分代) | 需 -XX:+UseShenandoahGC 显式开启 |
| 目标 | 亚毫秒级停顿 | 亚毫秒级停顿 |
两者殊途同归:都用读屏障 + 转移实现并发转移,只是记录"对象搬去了哪里"的方式不同。
参数与使用
ZGC
-XX:+UseZGC # 启用 ZGC(JDK 15+,21 起支持分代)
-XX:ZCollectionInterval # 回收间隔(秒)
-XX:ZAllocationSpikeTolerance # 分配峰值容忍度
-Xlog:gc* # GC 日志Shenandoah
-XX:+UseShenandoahGC # 显式启用
-XX:ShenandoahGCMode= # 模式(默认)适用场景
| 场景 | 推荐 |
|---|---|
| 超大堆(几十 GB+)、低延迟 | ZGC / Shenandoah |
| 高吞吐、堆不大 | Parallel GC(高吞吐优先) |
| 中间规模、兼顾停顿 | G1 |
| JDK 8 环境 | 无法用 ZGC(需 JDK 15+/21+),退而求其次用 G1 |
注意事项
- 内存开销:ZGC 需要额外空间保存指针信息与转发表,堆内存占用比 G1 高
- CPU 开销:读屏障加在每次对象访问路径上,CPU 占用上升
- 分代 ZGC:JDK 21 起分代 ZGC 大幅降低 CPU 开销并提升吞吐,是长期演进方向
- 兼容性:需确认所用 JDK 版本与依赖库对 ZGC/Shenandoah 的支持
常见问题
- ZGC 为什么停顿不随堆增长? 标记与转移全部并发,转移修复通过读屏障分摊到对象访问时进行,与堆大小无关。
- ZGC 与 G1 谁更好? 没有绝对优劣。ZGC 延迟更低但内存/CPU 开销更大,G1 平衡性更好。小堆用 G1 更划算,大堆低延迟选 ZGC。
- Shenandoah 与 ZGC 的区别? 都全并发+读屏障,区别在于 ZGC 用染色指针、Shenandoah 用转发指针;ZGC 是 JDK 15+ 默认可选回收器,Shenandoah 需显式开启。