shenandoah的concurrent evacuation阶段是并发搬迁存活对象至空闲region,而非清理;清理由concurrent cleanup阶段完成,其核心依赖brooks指针与硬件cas原子更新保障多线程竞争下的安全搬迁。

Shenandoah 的 Concurrent Evacuation(并发回收) 阶段并非“清理”,而是将回收集(Collection Set)中存活对象 并发地复制搬迁 到空闲 Region 中。真正负责“清理无用内存”的阶段是 Concurrent Cleanup。你提到的“并发清理”若指 Evacuation 阶段,那它本质上是高竞争、需强同步保障的搬迁过程——线程竞争与硬件 CAS 保护正是其稳定运行的关键支撑。
并发搬迁中的核心竞争点
多个 GC 线程和所有应用线程同时访问同一对象:GC 线程要读取旧对象、复制内容、更新转发指针;应用线程可能正通过旧引用读写该对象。这种读-写-改的混合访问天然引发竞态。典型竞争场景包括:
- 两个 GC 线程几乎同时尝试为同一个对象执行搬迁,导致重复复制或指针覆盖
- 应用线程在 GC 线程写入转发指针前读取对象头,拿到未初始化的指针值
- 应用线程正在写入对象字段,而 GC 线程已开始复制——需保证复制的是“一致快照”
Brooks Pointer + CAS 是协同机制,不是替代关系
Shenandoah 在对象头前插入 Brooks Pointer(一个额外的引用字段),默认指向对象自身。搬迁时,GC 线程需原子地将该指针从“指向自己”改为“指向新副本”。这个修改必须是原子的,否则应用线程可能看到中间态(如 null 或半写入地址)。因此:
- JVM 底层使用 硬件 CAS(Compare-and-Swap)指令 执行指针更新,例如 x86 上的
CMPXCHG,ARM 上的LDXR/STXR - CAS 失败说明有其他线程抢先完成搬迁,当前线程可直接读取新指针并跳过复制
- 所有对 Brooks Pointer 的读取都受 读屏障(Load Barrier) 拦截,屏障内会检查指针是否已重定向,未完成则触发等待或协助搬迁
为什么不能只靠软件锁或顺序化?
全局锁或区域锁会严重抵消并发收益,违背低延迟设计目标。而 CAS 提供无锁(lock-free)保障:
- 单次 CAS 延迟在纳秒级,远低于毫秒级 STW 成本
- 失败重试开销可控,实践中绝大多数对象只需一次 CAS 即可成功
- 现代 CPU 对 CAS 有良好优化(如缓存行锁定、快速重试路径),多核扩展性好
实际部署需关注的硬件与 JVM 层面协同
CAS 效能依赖底层支持。若运行环境不满足,可能退化或报错:
- 启用 Shenandoah 必须确保 CPU 支持对应原子指令(如 x86_64、AArch64),32 位 ARMv7 不支持
- 开启
-XX:+UseShenandoahGC后,JVM 会在启动时校验 CAS 可用性;若检测到不安全的虚拟化环境(如某些嵌套虚拟机),可能拒绝启用 - 高争用场景下(如大量短命大对象集中晋升),可配合
-XX:ShenandoahGuaranteedGCInterval=1000控制回收节奏,间接缓解 CAS 冲突密度











