ZGC通过将元数据编码进指针高位空闲比特实现无额外内存开销的并发标记,复用x86-64虚拟地址高4位(如42–45位)作为颜色位,地址与状态合一;所有元数据变更通过原子指针更新完成,读屏障靠颜色位触发轻量“自愈”逻辑,STW时间与堆大小解耦。

ZGC 在 64 位 Java 进程中,不额外分配内存存 GC 状态,而是把 Marked0、Marked1、Remapped、Finalizable 这四类元数据直接编码进指针值的高位空闲比特里——本质是让“地址”和“状态”合二为一。
为什么高位能用?
现代 x86-64 Linux 系统实际只使用虚拟地址的低 42–48 位(对应 4TB–256TB 寻址空间),高十几位长期为 0。ZGC 恰好复用其中连续的 4 位(如第 42–45 位),作为元数据域。这些位不参与物理寻址,也不影响地址有效性。
- JDK 15+ 默认使用
Z_ADDRESS_BITS = 42,地址掩码为(1ULL << 42) - 1 - 元数据掩码为
Z_METADATA_MASK = ((1ULL << 4) - 1) << 42 - 真实地址提取必须用
ptr & Z_ADDRESS_MASK,不能用右移操作,避免非对齐访问或符号扩展错误
染色指针不是“标记对象”,而是“标记指针”
传统 GC 往对象头或独立 bitmap 写标记,ZGC 完全绕开这些路径。每个指向堆对象的引用(如字段、栈变量、寄存器中的值)本身就是一个“带颜色的地址”。比如:
-
0x0000123456780000→ Remapped 视图(高 4 位为0001) -
0x1000123456780000→ Marked0 视图(高 4 位为1000) -
0x0100123456780000→ Marked1 视图(高 4 位为0100)
这三者可能映射到同一块物理内存页,仅靠虚拟地址别名区分语义。CPU 加载指针时,状态已随地址一同进入寄存器。
立即学习“Java免费学习笔记(深入)”;
状态变更靠原子指针更新,无需锁或屏障
ZGC 所有元数据变更都发生在指针复制路径上,例如 GC 线程将一个 Marked0 指针改为 Remapped,实质是生成新值写入引用字段,而非修改原对象头。
- 应用线程读取时看到的是某个确定快照,不存在 ABA 问题
- 没有共享标记位或全局 bitmap,天然规避 false sharing 和跨核缓存同步压力
- JVM 用汇编指令(如
mov+and+or)直接操作指针值,全程无内存屏障
读屏障靠颜色位触发“自愈”逻辑
JIT 编译器在 aload、getfield 等字节码处内联轻量汇编检查:
- 若检测到
Marked0/Marked1:将对象加入本地标记栈,继续运行,不阻塞 - 若检测到
Remapped:查转发表拿到新地址,用原子指令(如xchg)更新该引用字段 - 整个过程是纯位运算 + 单次内存访问,平均开销低于 1 纳秒
这种设计使初始标记仅需扫描 Roots,STW 时间与堆大小完全解耦。


















