连载中 10/20

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

2026-09-15 · 7 阅读 · 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)

热门推荐

连载中 11/22

主从搭建实操:从零配出一主两从

光讲原理不过瘾?手把手搭一主两从:my.cnf 六个参数、复制账号、GTID、CHANGE REPLICATION SOURCE TO、SHOW REPLICA STATUS 验收,附翻车排查清单。

#MySQL#主从复制#GTID#主从搭建#高可用
2026-05-07 · 10101 阅读 · 0 评论 · 0 赞
连载中 16/22

连接池:HikariCP 参数与连接风暴

连接池不是越大越好:8 核机器配 1000 连接反而更慢的数学原理,HikariCP 四个必调参数,maxLifetime 与 wait_timeout 的隐形陷阱。

#MySQL#连接池#HikariCP#maxLifetime#连接风暴
2026-05-10 · 9873 阅读 · 0 评论 · 0 赞
连载中 4/16

缓存穿透:恶意 ID 打穿 MySQL 的四道防线

请求的数据在缓存和数据库里都不存在时,缓存形同虚设。聊聊参数校验、空值缓存、布隆过滤器、限流熔断四道防线的原理与组合打法。

#Redis#缓存穿透#布隆过滤器#高可用
2026-05-16 · 9294 阅读 · 21 评论 · 287 赞