停顿与堆大小解耦
G1 已经把停顿压到 200ms 级,但堆越大、标记越久,停顿还是随堆涨。ZGC 做到了另一件事:停顿时间不随堆大小增长——8G 是它,128G 还是它,STW 常年在一毫秒以内。版本线记一下:JDK 11 实验特性,JDK 15 转正,JDK 21 的分代 ZGC 全面成熟。大堆低延迟场景(交易撮合、实时风控、广告竞价)第一次有了放心选项。
着色指针:引用自带状态
传统收集器把 GC 状态记在对象头或外部表里,ZGC 的脑洞是直接把状态写进指针本身——64 位指针只用低 44 位寻址,高位的几十个比特是白送的,ZGC 拿其中几位存标记状态(Marked0/Marked1/Remapped)。一个指针既指向对象,又自带"这个对象在 GC 里处于什么阶段"的元数据。指针即元数据,这是 ZGC 一切魔法的起点,也解释了它当年只支持 64 位平台。
读屏障:访问时顺手自愈
// 读屏障(伪代码):每次从堆里加载一个引用都会经过这段逻辑
Object ref = load(obj.field);
if (isBadColor(ref)) { // 指针颜色是旧的(对象已被搬走)
ref = loadFromForwardTable(ref); // 查转发表,拿到新地址
fixupAndStore(obj.field, ref); // 自愈:把旧引用原地修成新值
}
return ref;
// 效果:并发搬家期间,业务线程访问到"半路"的对象也没事——
// 第一次访问自动修正,之后走的就是新地址
// 代价:每个引用加载多一小段检查,全局吞吐损失约 5%~10%配合着色指针,读屏障解决了并发整理的死结:对象正在被搬,业务线程偏偏这时候来访问怎么办?G1 的答案是搬家时停顿,ZGC 的答案是访问时自愈——旧引用按转发表修正后照常返回,业务线程几乎无感。
全程并发的三段式
ZGC 的标记、转移、重定位三段全部并发执行,STW 只剩"扫 GC Roots"这一下——根集合小,停顿自然是亚毫秒。对象搬完,旧的引用不急着全改,等下次被访问时读屏障顺手修(惰性重定位),把修正成本摊薄到日常运行里。
代价与启用
世上没有免费的停顿:读屏障吃吞吐(约 5%~10%),未分代前标记全堆吃 CPU,对象搬家期间需要预留挪腾空间(内存占用比 G1 高一截)。JDK 21 的分代 ZGC 补上了最后一块短板——新生代对象单独快速回收,标记成本大幅下降,吞吐已接近 Parallel。启用只需 -XX:+UseZGC(JDK 21 起默认分代)。选型一句话:停顿敏感且堆大,ZGC;追求综合性价比,G1。收集器讲完了,下一篇开始"看得见"的部分——把 GC 的每次动作翻译成人话:GC 日志。
评论 (0)