ZGC染色指针是将GC状态(Marked0/Marked1/Remapped/Finalizable)直接编码进64位指针高位空闲比特,通过位运算快速解码地址与状态,替代对象头读写和内存屏障,实现亚毫秒停顿。

ZGC 染色指针不是给对象“上色”,而是把变量状态直接编码进 64 位指针的高位空闲比特中——它用位存储替代对象头读写,让状态标记变成一次位运算,不访问内存、不触发屏障、不依赖同步。
染色指针本质是地址+状态的原子封装
在 Linux x86_64 上,虚拟地址实际只用低 42–48 位(对应 4TB–256TB 地址空间),高 16 位长期为 0。ZGC 借用其中连续 4 位(如第 42–45 位)作为元数据域,定义四种状态:
- Marked0 / Marked1:交替用于并发标记,规避 ABA 问题,避免漏标
- Remapped:表示该指针指向旧地址,但对象已迁移,需经读屏障重定向
-
Finalizable:标识对象含
finalize()方法,需特殊入队处理
这些位不是附加标记,而是指针值的一部分。例如一个原始地址 0x0000123456780000,ZGC 内部可能存为 0x0000123456780002(Marked0 置位),解码时只需 ptr & ~0xF 即得真实地址。
状态提取必须靠掩码,不能靠移位
高位染色位的位置随 JDK 版本变化(JDK 11 与 JDK 15 的 Z_METADATA_SHIFT 不同),硬编码移位会出错。正确做法是使用 JVM 提供的固定掩码:
- 地址提取:
address = ptr & Z_ADDRESS_MASK(Z_ADDRESS_MASK = (1ULL ) - 状态提取:
color = ptr & Z_METADATA_MASK(Z_METADATA_MASK由版本决定) - 禁止使用
ptr >> 4或ptr >> 42:可能引入非对齐访问、分支预测失败或符号扩展错误
变量状态标记完全脱离对象头
传统 GC(如 G1、CMS)需暂停线程去读对象头的 mark word;ZGC 则在每次加载引用时,由读屏障自动检查指针颜色:
- 若加载到带 Marked0 的指针,且对象未被标记,就当场标记并推入标记栈(不 STW)
- 若加载到带 Remapped 的指针,就读取转发表、更新指针值、清除 Remapped 位,再返回新地址
- 所有字段访问(
getfield、aload)都隐式触发该逻辑,应用线程“边跑边修”
这意味着:变量是否存活、是否已迁移、是否待终结——这些状态全由它被引用的方式决定,而非它自身内容。
绕过读屏障就等于绕过状态管理
以下操作会跳过染色指针校验,导致状态失效:
- 使用
Unsafe.getLong(obj, offset)直接读内存,不走 JIT 插入的 barrier - JNI 中将
jobject当作裸地址做算术运算(如(char*)env->GetObjectRefType(obj) + 8) - 启用
-XX:-UseZGCReadBarrier(禁用读屏障后 ZGC 无法工作)
这类代码不会报错,但可能读到已回收内存、漏标对象、或访问到未重映射的旧地址——静默崩溃或数据错乱。










