Shenandoah通过写屏障(而非读屏障)实现并发转移,每次写操作触发转发检查与对象复制,开销纳秒级且可控;读操作直接通过Brooks指针跳转,无需屏障,显著降低读密集型负载成本。

Shenandoah 的读写屏障确实会带来一定性能开销,但这个开销是可控且有针对性的,关键在于它用少量运行时成本换来了极低的 GC 停顿时间。
写屏障是 Shenandoah 的核心开销来源
Shenandoah 在 JDK 13 之前采用引用不变性设计,对象转移过程中需保证 Mutator(应用线程)始终看到一致状态。为此,它在每次对象字段写入前插入写屏障(Write Barrier),主要做两件事:
- 检查目标对象是否已转发(即是否已在新位置完成复制);
- 若尚未转发,且当前处于并发转移阶段,则触发转发逻辑——将对象复制到新位置,并更新旧对象头中的 Brooks 转发指针;
- 无论是否转发,最终都确保写操作落在正确的对象副本上(通常是新位置)。
这意味着每个 obj.field = value 都多了一次对象头检查和可能的转发动作。高频写场景(如缓存更新、日志聚合)下,这部分开销较明显,但现代 CPU 分支预测和屏障代码高度优化后,单次开销通常在纳秒级。
读屏障在 Shenandoah 中默认不启用
与 ZGC 不同,标准 Shenandoah 实现不依赖读屏障。它通过 Brooks 转发指针 + 写屏障组合,让 Mutator 在读取对象时直接通过旧对象头跳转到新对象——无需每次读都检查。这大幅降低了读密集型应用(如只读服务、报表查询)的运行时负担。
不过,某些特殊模式(如启用“卸载类”或特定调试选项)可能引入轻量级读检查,但生产环境默认关闭。
屏障开销 vs STW 成本:典型的权衡取舍
屏障带来的微小延迟,本质是把原本集中爆发的 STW 时间,“摊薄”到整个 GC 周期中。例如:
- G1 在压缩阶段可能停顿 50–200ms(随堆增大而上升);
- Shenandoah 全程仅初始标记、最终标记等极短 STW(通常
- 应用线程感知不到“卡顿”,即使吞吐略降 5%–10%,对延迟敏感系统仍是更优选择。
实际压测表明,在 32GB 堆、QPS 5k 的交易服务中,Shenandoah 相比 G1 吞吐下降约 7%,但 P99 延迟从 85ms 降至 4.2ms —— 这正是屏障设计所服务的核心目标。
可调优的关键点
若实测发现屏障开销偏高,可通过以下方式缓解:
- 避免频繁修改同一对象的多个字段(合并写操作,减少屏障触发次数);
- 对只读集合(如配置缓存)使用不可变对象或
final字段,绕过写屏障; - 调整
-XX:ShenandoahGuaranteedGCInterval控制 GC 频率,避免过于激进的回收节奏放大屏障压力; - JDK 17+ 支持
-XX:+ShenandoahOptimizeFill等优化标志,可进一步降低转发路径开销。


















