ZGC的染色指针将Marked0、Marked1、Remapped、Finalizable四类元数据编码进64位指针高42–45位空闲比特,实现地址与状态合一;因x86-64 Linux仅用低42–48位寻址,高位可安全复用,不占内存、不访问对象头。

ZGC 的“彩色指针”不是给对象上色,而是把元数据直接塞进 64 位引用地址的高位空闲比特里——地址和状态合二为一,不占额外内存,也不碰对象头。
为什么高位能用?
现代 x86-64 Linux 系统实际只使用虚拟地址的低 42–48 位(对应 4TB–256TB 寻址空间),高十几位长期为 0。ZGC 恰好复用其中连续 4 位(如第 42–45 位)作为元数据域,这些位不参与物理寻址,不影响地址有效性。
- JDK 15+ 默认使用 Z_ADDRESS_BITS = 42,地址掩码为
(1ULL - 元数据掩码为
Z_METADATA_MASK = ((1ULL - 真实地址必须用
ptr & Z_ADDRESS_MASK提取,不能用右移——避免非对齐访问或符号扩展错误
四种颜色位分别代表什么?
这 4 个比特组合出四种关键状态,每个都对应 GC 阶段的语义:
- Marked0 / Marked1:交替用于并发标记,规避 ABA 问题,确保跨周期标记不混淆
- Remapped:表示该指针指向旧地址,但对象已迁移;读屏障会查转发表、更新引用并清除该位
-
Finalizable:标识对象含
finalize()方法,需特殊入队处理
例如:0x0000123456780000 是 Remapped 视图(高 4 位为 0001),0x1000123456780000 是 Marked0 视图(高 4 位为 1000),二者可能映射到同一物理页,仅靠虚拟地址别名区分语义。
状态变更如何做到无锁?
所有元数据更新都发生在指针复制/加载路径上,不修改对象本身,也不依赖 CAS 或内存屏障:
- GC 线程将一个引用从 Marked0 改为 Remapped,本质是生成新值写入字段,而非更新原对象
- 应用线程读到的是某个确定快照;指针值本身是“标签+地址”的不可变组合,天然规避 false sharing 和锁竞争
- 没有全局 bitmap 或共享标记位,也就没有跨核缓存同步压力
读屏障怎么靠颜色位工作?
读屏障不是函数调用,而是 JIT 在 aload、getfield 等字节码处内联的一小段汇编,只做两件事:
- 检查加载出的指针是否含 Marked0/Marked1:若是且对象未标记,就当场推入本地标记栈(不 STW)
- 检查是否含 Remapped:若是,查转发表拿到新地址,用原子指令(如
xchg)更新引用并清除颜色位 - 整个过程轻量、无锁、可内联,开销接近一条
test + jz指令
绕过读屏障的操作(如 Unsafe.getLong、JNI 中裸地址运算)会跳过状态校验,导致漏标或访问已回收内存——这类代码不会报错,但破坏 GC 正确性。

















