shenandoah在物理内存告急时不会崩溃,而是通过预检分配、触发退化gc(degenerate gc)和延迟提交(lazy commit)三层机制主动降级为stw full gc,确保jvm继续运行;仅当系统级资源彻底耗尽时才抛出底层oom错误。

Shenandoah 在并发复制阶段遭遇物理内存告急(即系统级内存不足,非 JVM 堆内 OOM)时,不会直接崩溃,而是通过多层防御机制主动降级、规避或转交处理,核心设计目标是“避免不可恢复的进程终止”。
Shenandoah 的内存压力响应机制
它不依赖操作系统在 malloc 失败时抛出 SIGSEGV 或直接 kill 进程,而是主动监控和干预:
- 本地内存分配前预检:Shenandoah 在为 Region 分配转发指针(forwarding pointer)、复制缓冲区或 GC 元数据时,会调用 os::commit_memory() 并检查返回值;若失败(如 mmap 返回 MAP_FAILED),立即触发 degenerate GC(退化 GC)而非硬崩溃。
- 自动触发退化 GC(Degenerate GC):这是 Shenandoah 的关键容错策略。一旦检测到内存分配失败或 GC 进程被严重阻塞(如复制线程因缺页长时间等待),它会中止并发阶段,快速切换至单线程、stop-the-world 的 Full GC 模式,在更可控的上下文中完成回收与压缩,释放已占用但未提交的内存页。
- 延迟提交(Lazy Commit)策略:Shenandoah 默认对 Region 内存采用 lazy commit —— 仅在首次写入时才真正向 OS 申请物理页。这大幅降低并发复制期的瞬时内存压力,避免“一次性占满”的风险;即使物理内存紧张,也只影响后续新 Region 的使用,已有工作可继续。
与传统 GC 的关键区别
不同于 CMS 或 G1 在元数据或卡表分配失败时可能陷入不确定状态,Shenandoah 将“内存分配失败”明确建模为一种 可恢复的 GC 中断事件:
- 它不假设底层内存永远可用,所有关键路径都包含分配失败的分支处理逻辑;
- 退化 GC 完成后,JVM 继续运行,应用线程恢复执行——用户感知通常是“一次较长的 STW”,而非进程退出;
- 若退化 GC 后仍无法获得足够内存(例如系统整体已耗尽),JVM 才会最终抛出
java.lang.OutOfMemoryError: Direct buffer memory或unable to create new native thread等底层错误,此时问题已超出 GC 控制范围,属于宿主机资源配置问题。
运维侧需配合的关键点
Shenandoah 的韧性需要合理配置与外部环境协同:
- 确保
-XX:MaxDirectMemorySize设置合理(尤其 Netty 类应用),避免 DirectByteBuffer 与 Shenandoah 共争本地内存; - 监控
/proc/meminfo中的MemAvailable,而非仅看Free;物理内存告急往往源于 page cache 或 slab 占用过高; - 启用
-XX:+UnlockDiagnosticVMOptions -XX:+PrintShenandoahStats,关注日志中degenerated、alloc_failure和uncommit相关统计,这是内存压力的真实信号。











