连载中 10/20

ZGC:亚毫秒停顿是怎么炼成的

2026-09-15 · 8 阅读 · 0 评论 · 0 赞

停顿与堆大小解耦

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 日志。

503

10 年全栈工程师 · 503咖啡馆主理人

#ZGC#着色指针#读屏障#亚毫秒停顿#分代ZGC

评论 (0)

相关推荐

连载中 12/20

排查四件套:jstack、jmap、jstat、jcmd 的实战分工

jstack 看线程在干什么,jmap 看堆里装了什么,jstat 看运行时在变什么,jcmd 是统一入口。四把刀各管一段,配合着用没有查不动的现场。

#jstack#jmap#jstat#jcmd#排查工具
2026-09-16 · 1 阅读 · 0 评论 · 0 赞
连载中 11/20

GC 日志:把回收过程翻译成人话

一行 GC 日志里塞着七种信息:谁触发的、收了哪、停了多久、活了哪些。加上 -Xlog 配置,再加上日志分析工具,GC 不再是只能盯监控曲线的黑盒。

#GC日志#Xlog#日志分析#GC监控#Full GC排查
2026-09-16 · 12 阅读 · 0 评论 · 0 赞
连载中 9/20

G1:把堆切成 Region 之后发生了什么

G1 不再物理分代,而是把堆划成两千多个 Region,谁垃圾多先收谁——停顿时间第一次变成可设置的参数。RSet 与混合回收,是理解 G1 的两把钥匙。

#G1收集器#Region#RSet#MixedGC#停顿模型
2026-09-15 · 5 阅读 · 0 评论 · 0 赞