shenandoah 并发标记无独立终止检查阶段,而是通过 satb 快照保障一致性、工作窃取实现分布式任务调度,并依赖队列耗尽与 satb 日志清空自然收敛;最终仅需短暂 stw 收尾处理残留日志。

Shenandoah 的并发标记阶段本身并不包含一个独立、显式的“终止检查(Termination Check)”阶段——这是 G1 GC 中标记过程的典型设计(如 Concurrent Mark Termination),而 Shenandoah 采用的是 基于 SATB(Snapshot-At-The-Beginning) + 并发遍历 + 工作窃取(Work Stealing)的标记模型,其“终止”逻辑是隐式、分布式且轻量级的,不依赖集中式轮询或全局屏障等待。
以下是该机制在底层的实际运作方式:
SATB 快照保障标记一致性
初始标记(Initial Mark)会为每个线程记录当前的 TAMS(Top-at-Mark-Start)指针,并将此时堆中所有存活对象作为初始根集快照。后续新分配对象默认视为“存活”,写入时通过写屏障(Write Barrier)将旧引用记录到 SATB 日志缓冲区。这使得并发标记无需冻结整个堆,也无需反复扫描新增引用链——只要遍历完初始快照中的可达图,就可认为标记完成。
工作队列耗尽即自然终止
Shenandoah 使用多个并发标记线程,共享一个全局标记任务队列(通常基于双端队列实现)。每个线程持续执行:
- 从本地队列或全局队列中取出待标记对象
- 遍历其引用字段,对未标记的引用对象打标并加入队列
- 若本地队列为空,则尝试“窃取”其他线程队列尾部的任务
当所有线程均无法再从任何队列中获取新任务,且 SATB 缓冲区也全部处理完毕(即无新增需重新扫描的脏引用),标记线程便自动退出。这个过程没有中心协调器,也不需要阻塞等待“所有线程报告完成”,而是靠空队列+空缓冲区+无新任务产生三重条件达成自然收敛。
无显式终止检查,但有最终同步点
虽然没有 G1 那样的 Concurrent Mark Termination 阶段,Shenandoah 在并发标记结束后,会在最终标记(Final Marking)阶段开头做一次轻量同步:
- 暂停用户线程(短暂 STW)
- 刷出并处理所有剩余 SATB 日志(确保未被并发标记线程覆盖的引用变更被补扫)
- 统计存活对象、构建回收集(Collection Set)
这步不是“检查是否终止”,而是兜底确认标记完整性,属于收尾动作,而非运行时的终止判定机制。
简言之:Shenandoah 的并发标记终止不是靠周期性检查或投票协议,而是靠任务队列枯竭 + SATB 日志清空 + 分布式工作窃取的自然收敛。它把“何时结束”的判断权交给任务调度本身,避免了集中式终止检查带来的竞争与延迟。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











