ZGC染色指针将GC状态直接编码进64位指针高位空闲比特,地址与状态合二为一;所有对象访问必须经读屏障校验颜色,实现TB级堆下亚毫秒停顿。

ZGC 的并发染色指针不是给对象“贴标签”,而是把 GC 状态直接塞进指针值本身——地址和状态合二为一,所有对象访问都靠读屏障自动“校验颜色”,从而实现 TB 级堆下亚毫秒停顿。
染色指针本质是高位复用,不是加标记
64 位 Linux 虚拟地址实际只用低 42–48 位(对应 4TB–256TB 寻址空间),高位长期为 0。ZGC 恰好借用其中连续 4 位(如第 42–45 位)编码四种状态:
- Marked0 / Marked1:交替用于并发标记,避免 ABA 问题
- Remapped:表示该指针指向旧地址,对象已迁移,需重定向
-
Finalizable:标识含
finalize()方法,需特殊入队
例如原始地址 0x0000123456780000,ZGC 内部可能存为 0x1000123456780000(高 4 位为 1000 表示 Marked0)。它不是附加信息,而是指针值不可分割的一部分。
读屏障是染色指针生效的唯一入口
每次 Java 字节码执行 aload、getfield、invokevirtual 等涉及引用的操作,JIT 都会插入轻量检查逻辑:
立即学习“Java免费学习笔记(深入)”;
- 读取指针高位颜色位
- 若为
Marked0且对象未标记,就当场标记并推入标记栈(不 STW) - 若为
Remapped,就查转发表、原子更新指针、清除 Remapped 位,再返回新地址
这个过程叫“指针自愈”——应用线程边运行边修复,无需全局暂停。禁用读屏障(如加 -XX:-UseZGCReadBarrier)等于让染色指针失效,ZGC 将无法工作。
状态提取必须用掩码,不能靠移位
高位位置随 JDK 版本变化(JDK 15 默认 Z_ADDRESS_BITS = 42,JDK 21 分代 ZGC 可能不同),硬编码右移会出错。正确方式是使用 JVM 固定掩码:
- 真实地址:
address = ptr & Z_ADDRESS_MASK(Z_ADDRESS_MASK = (1ULL << 42) - 1) - 颜色状态:
color = ptr & Z_METADATA_MASK(Z_METADATA_MASK = 0xF000000000000000L或类似高位掩码)
禁止写 ptr >> 42 或 ptr >> 4:可能触发符号扩展、非对齐访问或分支预测失败。
绕过读屏障就等于绕过 GC 状态管理
以下操作跳过染色指针校验,极易引发静默错误:
- 用
Unsafe.getLong(obj, offset)直接读内存字段 - JNI 中将
jobject当作裸地址做算术运算(如(char*)env->GetObjectRefType(obj) + 8) - 大量使用
GetPrimitiveArrayCritical锁定数组后直接操作底层指针
这类代码不会立即报错,但可能导致漏标、访问已回收内存、或读到未重映射的旧地址——问题往往在大负载、长时间运行后才暴露。


















