ZGC通过染色指针实现亚毫秒级停顿:利用64位指针高42–45位存储Marked0/Marked1/Remapped/Finalizable四类GC状态,地址与元数据分离提取,避免对象头访问和额外内存操作,使标记、重映射等变为纯位运算。

染色指针不是给对象“上色”,而是把 GC 状态信息直接编码进 64 位指针的高位空闲位里。它绕开了对象头、bitmap 或 card table,让标记、重映射等操作变成纯位运算,从而支撑 ZGC 在 16TB 堆下仍保持毫秒级停顿。
为什么能用指针高位存状态?
现代 x86-64 Linux 系统实际只使用虚拟地址的低 42~48 位(对应 4TB~256TB 寻址空间),高十几位长期为 0。ZGC 恰好复用其中连续的 4 个高位 bit,例如第 42–45 位,作为元数据存储区。
- 这 4 位不参与地址计算,不影响寻址能力,也不需要硬件修改
- ZGC 实际管理堆上限为 4TB(42 位地址空间),但通过虚拟内存映射技术,可支持到 16TB 甚至更大——关键在于操作系统允许将不同物理页映射到同一段逻辑地址空间,ZGC 利用这一点扩展可用范围
- 地址提取必须用 ptr & ~0xF(屏蔽低 4 位),不能用右移,避免非对齐访问或分支预测开销
四个元数据位分别代表什么?
这 4 位不是随意定义的,每种组合对应 GC 生命周期中的一个明确语义:
- Marked0 / Marked1:交替用于并发标记阶段,解决 ABA 问题;一次标记用 Marked0,下一轮用 Marked1,避免误判对象状态
- Remapped:表示该指针指向的是重定位后的新地址;旧指针未更新时,靠此位识别是否需触发读屏障跳转
-
Finalizable:标识对象含有
finalize()方法,需在回收前加入终结队列,由专用线程处理
如何从指针中安全提取地址和状态?
所有操作必须零内存访问、无函数调用、无条件分支——这是 ZGC 实现亚毫秒停顿的前提。
- 地址提取:
addr = ptr & Z_ADDRESS_MASK(Z_ADDRESS_MASK = (1ULL ) - 状态提取:
color = ptr & Z_METADATA_MASK(Z_METADATA_MASK = 0xFULL ) - 判断是否已重映射:
(ptr & REMAPPED) != 0,结果直接参与后续跳转逻辑 - Java 层完全无感:应用代码看到的仍是“普通”对象引用,JVM 在加载/存储指令路径中自动插入读屏障处理染色逻辑
与传统标记方式的根本区别
传统 GC 把标记写进对象头或独立 bitmap,每次读对象都要多一次内存访问;ZGC 把标记“随身携带”在指针上,只要拿到指针,状态就已确定。
- 无需访问对象本身即可判定其 GC 阶段,极大减少缓存污染和内存带宽压力
- 避免了对象头写屏障带来的同步开销,也省去了维护外部标记结构的内存与原子操作成本
- 本质上是把“标记对象”变成了“标记引用”,使并发标记真正意义上成为引用图遍历,而非对象图遍历











