分代gc默认开启因传统分代假设成立,但zgc和shenandoah为实现超低暂停而关闭它,因现代应用中缓存、连接池等使对象生命周期变长,导致年轻代频繁晋升、老年代提前饱和。

分代GC为什么默认开启,但ZGC和Shenandoah却主动关闭它
因为分代假设(多数对象朝生暮死)在现代应用中越来越不成立——缓存、连接池、长生命周期DTO大量存在,导致年轻代频繁晋升、老年代提前饱和。ZGC和Shenandoah的设计目标是超低暂停(
-
-XX:+UseZGC和-XX:+UseShenandoahGC默认隐式禁用分代:不会划分young/old空间,整个堆统一管理 - 如果强行加
-XX:+UseG1GC -XX:+UseStringDeduplication这类组合,G1仍属分代,但ZGC/Shenandoah加了也无效——JVM会忽略或报错 - 分代GC(如G1、Parallel GC)依赖
Remembered Set跟踪跨代引用;ZGC用colored pointers、Shenandoah用Brooks pointer实现并发标记,绕开了RSet维护成本
G1的Mixed GC和ZGC的Pauseless GC根本不是一回事
很多人看到“混合回收”就以为ZGC也是类似逻辑,其实G1的Mixed GC仍是Stop-The-World阶段,只是回收范围扩大到部分老年代Region;而ZGC的每次GC周期里,Initial Mark和Final Mark虽有STW,但合计通常
- G1的
Mixed GC触发条件是老年代占用率达InitiatingOccupancyPercent(默认45%),之后持续触发直到老年代清理充分 - ZGC没有“混合”概念:每次GC都扫描全堆,但通过
load barrier在对象加载时增量更新标记状态,避免全局扫描 - Shenandoah同理,但用
Brooks forwarding pointer实现对象移动时的并发访问,代价是每个对象头多2个引用字段
启用ZGC或Shenandoah前必须检查JDK版本和Linux内核特性
它们不是“开箱即用”的替代品。ZGC从JDK 11开始实验性支持,但直到JDK 17才转正;Shenandoah在JDK 12进入主线,JDK 15起默认启用。更重要的是,它们严重依赖OS底层能力:
- ZGC要求Linux内核≥4.14(需
mmap(MAP_FIXED)支持大页重映射),否则回退到保守模式,暂停时间可能飙升 - Shenandoah在旧内核上可运行,但若不支持
userfaultfd(≥4.3),则无法使用degenerated GC路径,遇到分配失败时会fallback到Full GC - 必须显式指定堆大小上限:
-Xmx16g,ZGC不支持动态堆伸缩;Shenandoah虽支持,但扩容过程本身会触发STW
GC日志里看不到“Young GC”字样,不代表没发生对象分配压力
不分代不等于不关注对象生命周期。ZGC和Shenandoah依然有TLAB(Thread Local Allocation Buffer)和PLAB(Promotion LAB),只是不再按代切分回收策略。如果你发现Allocation Stall增多或GC cycle频率异常升高,问题往往出在应用层:
立即学习“Java免费学习笔记(深入)”;
- 短生命周期对象暴增(如循环内反复new HashMap)会导致TLAB频繁耗尽,触发同步分配,拖慢线程
- 大对象(>256KB)直接进
large object space,ZGC对其标记/移动开销更大,且不能被压缩——容易碎片化 - Shenandoah的
Update Refs阶段若遇到大量弱引用(WeakReference、SoftReference),会显著延长该阶段耗时,需检查缓存框架是否过度依赖软引用










