zgc通过将marked0/marked1/remapped/finalizable状态直接编码进64位指针高4位,结合虚拟内存多重映射与读屏障,实现并发标记与重映射,仅初始标记和最终重映射需stw。

ZGC 的着色指针(Colored Pointers)不是附加的元数据结构,而是把状态信息直接“塞进”64 位指针的高位里。它绕开了传统 GC 必须访问对象头或独立位图的开销,让每次状态判断变成一次位运算——纳秒级完成,且完全不依赖内存读取。
为什么非得把颜色塞进指针里?
传统 GC 要知道一个对象是否被标记、是否已转移,得去查对象头或全局位图。这不仅多一次内存访问,还容易引发缓存竞争和伪共享。ZGC 反过来想:既然指针本身要被加载,那干脆让它“自带身份”。只要应用线程一读引用,颜色位就自然暴露,无需额外动作。
- Linux/x86-64 下,64 位虚拟地址实际只用前 46–48 位寻址(支持 64TB–256TB 物理内存),高位长期闲置
- ZGC 拿出其中 4 位(42–45 位常见)编码四种状态:Marked0、Marked1、Remapped、Finalizable
- 剩余高位保留,兼容未来扩展,也避免与内核地址空间冲突
四种颜色分别管什么?
这四个标志位不是随意定义的,各自承担明确的并发阶段职责:
- Marked0 / Marked1:交替使用,用于三色标记中的“存活标记”。本轮用 Marked0,下轮自动切到 Marked1,天然规避标记翻转问题
- Remapped:表示该指针已指向迁移后的新地址。读屏障靠它快速判断是否需要查转发表;未置位则说明仍指向旧位置,需按需修复
- Finalizable:标识对象仅能通过 finalize() 访问,GC 可据此延迟回收,避免误清
颜色怎么“动”起来?靠内存多重映射
单纯改指针高位没用——操作系统会把它当非法地址拒绝访问。ZGC 底层用的是 Linux mmap 的虚拟内存技巧:
- 将同一块物理内存,同时映射到三个不同虚拟地址段(如 0x00…、0x10…、0x20…)
- 每个段对应一种“视图”:原始视图、标记视图、重映射视图
- 修改指针高位,本质是切换访问哪个虚拟地址段,物理数据不动,但语义立刻变化
这就实现了“指针自愈”:旧指针读取时触发读屏障,屏障根据 Remapped 位决定跳转到哪个映射段,自动拿到新地址并更新指针值。
它如何消灭 STW?
着色指针本身不直接消除停顿,但它为“无锁并发”提供了基础设施:
- 标记不用 STW 扫根:GC 线程从 Roots 开始遍历,靠 MarkedX 位跳过已标对象;应用线程访问对象时,读屏障顺手补标——标记是“摊在每一次读上”的
- 重映射不用 STW 更新所有引用:只要指针带 Remapped=0 且对象已迁移,读屏障当场查表修正,后续访问就是干净新地址
- 停顿只留给极轻量操作:初始标记 Roots、再标记弱引用、回收页面元数据——这些与堆大小无关,只跟线程栈深度、静态变量数等固定量有关











