染色指针能大幅削减STW时间,因其将对象存活、已移动等状态直接编码在64位指针高4位,应用线程读引用时无需停顿等待扫描对象头,仅需解析指针颜色位即可实时判断,配合读屏障实现并发标记与重定位。
为什么染色指针能砍掉大部分 STW 时间
因为 ZGC 把对象“是否存活”“是否已移动”这些关键状态直接塞进
的高位里,而不是去读对象头。应用线程每次加载引用(比如
)时,不用停顿等待 GC 扫描完对象头再决定要不要标记——状态就在指针里,一读即得。
传统 GC(如 G1)要遍历亿级对象头才能完成标记,而 ZGC 只需检查指针颜色位,硬件级快。这也是它能在
堆下仍压住停顿在
内的底层原因。
染色位具体怎么编码和解码
ZGC 在 JDK 15+ 默认用指针的高 4 位(第 60–63 位)做染色位,常见组合包括:
:未标记(White),对象刚分配或尚未被 GC 触及
或
:Marked0 / Marked1(Gray),表示正参与并发标记
:Remapped(Black + 已重定位),说明对象已被搬走,当前指针是旧地址,需通过读屏障跳转
解码真实地址就是清掉这几位:
。注意:不是所有版本都固定用高 4 位,JDK 21+ 分代 ZGC 对位域做了微调,但原理一致。
读屏障 Load Barrier 是怎么配合染色指针工作的
染色指针自己不保证一致性——如果应用线程刚好读到一个
的指针,但还没更新到新地址,就会出错。这时候
就是兜底机制:
每次
操作(如字段访问、数组索引)前自动插入一小段检查逻辑
若发现指针带
位,就查全局
拿新地址,原子更新本地寄存器中的指针值
这个过程极快(通常几条 CPU 指令),且完全由 JIT 编译器内联生成,不引发函数调用开销
没有读屏障,染色指针只是个高效标记;有了它,才真正实现“边跑边搬、边搬边用”。这也是 ZGC 不依赖写屏障(Write Barrier)的关键——漏标靠读屏障现场补,而非靠写屏障全程拦截。
实际部署时最容易被忽略的三个硬性前提
染色指针不是开箱即用的技术,它对运行环境有强约束:
必须是
,且操作系统支持大地址空间(Linux 需启用
,Windows 需开启
)
堆大小超过
时,
会隐式要求使用
;没配会导致启动失败并报错
JDK 版本必须 ≥
(实验性),生产推荐 ≥
(稳定支持
),
才完整支持分代 ZGC 和
堆
很多团队在压测时发现停顿突然飙升,最后定位是 OS 层没开大页,或者用了 32 位容器镜像——染色指针的位域直接失效,ZGC 退化为 fallback 收集器,反而更慢。
64 位指针obj.field16TB10ms0000100001000010uintptr_t decode_pointer(uintptr_t p) { return p & ~0xF000000000000000UL; }Remapped=1Load BarrierloadRemappedForwarding Table64 位 JVMhugetlbpagelarge pages4GB-XX:+UseZGCLarge PagesFailed to allocate large page memory11154TB21+16TB