zgc并发标记通过三色标记+着色指针+读屏障实现硬件级协同去重,避免重复扫描与漏标;以页面为单位并行遍历,确保活对象图全覆盖且低停顿。

ZGC 并发标记期的活对象图遍历,本质是图论中“从 GC Roots 出发的可达性遍历”在并发环境下的安全实现。它不追求传统意义上的“去重”,而是通过硬件级协同机制规避重复工作、防止漏标——这才是“高能去重”的真实含义:不是事后筛重,而是一开始就让多个线程天然不重叠、不遗漏。
可达性图结构决定遍历起点与边界
Java 堆在图论中是一个有向图 G = (V, E),其中:
- V 是所有对象(含 GC Roots),每个对象是图中一个顶点;
- E 是引用关系,若对象 A 持有对 B 的强引用,则存在有向边 A → B;
- GC Roots 构成初始顶点集 R ⊆ V,包括栈帧局部变量、静态字段、JNI 引用等;
- “活对象”即从 R 出发、沿有向边可到达的所有顶点构成的子图 Glive ⊆ G。
因此,并发标记的目标,就是高效、一致地识别出 Glive 的全部顶点,且全程允许 mutator(业务线程)修改 E(即新增/断开引用)。
三色标记是图遍历的状态机抽象
ZGC 复用三色标记作为并发遍历的状态协议:
- 白色顶点:尚未被任何 GC 线程访问,处于“未探索”状态;
- 灰色顶点:已被访问,其邻接边(引用)尚未完全扫描,是“待扩展”状态;
- 黑色顶点:自身及所有直接邻接顶点均已扫描完成,是“已收敛”状态。
该状态机天然支持并发:多个 GC 工作线程可同时从灰色集合中取顶点、将其邻接白顶点染灰、自身染黑,只要保证状态跃迁原子性(ZGC 依赖着色指针高位 + 原子 CAS 实现),就不会出现状态冲突或重复入队。
着色指针 + 读屏障实现硬件级协同去重
ZGC 不依赖全局队列或锁来协调线程,而是把“去重逻辑下沉到指针层面”:
- 每个对象指针的高位(如 4 位)编码其标记状态(Marked0 / Marked1),该状态随对象本身存储在内存中,所有线程通过同一物理地址读取时看到一致视图;
- 当 GC 线程尝试将某白色对象染灰时,执行原子 CAS:仅当指针当前为白色才成功写入灰色状态,失败即说明其他线程已处理——这是硬件级的“一次生效”保障;
- 读屏障(Load Barrier)在业务线程解引用对象前插入检查:若发现目标对象处于“正在转移中”或“已标记但未重定位”,则触发即时重定位或转发,确保后续访问始终命中最新状态,避免因并发修改导致的路径分裂或跳过。
这种设计使“多个线程扫描同一对象”的概率趋近于零,不是靠调度规避,而是靠状态位+原子操作+屏障拦截,在 CPU 缓存一致性协议(如 MESI)支持下完成自然去重。
页面粒度与并行任务划分降低图结构干扰
ZGC 将堆划分为固定大小页面(如 2MB),每个页面作为并发标记的基本调度单元:
- GC 工作线程按页面分配任务,每个页面内对象拓扑相对局部,减少跨页引用带来的缓存抖动;
- 页面间无依赖,可完全并行处理,避免传统基于对象图深度优先遍历中的栈竞争和递归同步开销;
- 即使 mutator 在标记中修改了跨页引用,读屏障会捕获该变更并通知标记线程补扫——这相当于在图动态演化中维持弱一致性遍历,而非强一致快照。
最终效果是:遍历覆盖完整可达子图,线程间无冗余扫描,无锁等待,停顿只发生在根集(GC Roots)扫描阶段,与图规模无关。










