Java常用GC算法为标记-清除、复制、标记-整理,分别适用于老年代、年轻代及特定场景;新生代用复制因存活率低、效率高但空间利用率减半;老年代避免标记-清除以防碎片化引发Full GC;G1混合回收实为细粒度复制;ZGC/Shenandoah等通过硬件特性实现并发整理,超越传统算法框架。

Java里常用的GC算法就这三种:标记-清除、复制、标记-整理
它们不是并列选项,而是针对不同分代(年轻代/老年代)和不同垃圾收集器组合使用的。选哪个不看“先进”,而看堆内存结构、对象存活率、停顿时间要求——比如 G1 在年轻代用复制,在老年代用标记-整理;ZGC 则完全绕开这些传统算法,走读屏障+染色指针路线。
为什么新生代默认用复制算法而不是标记-清除
因为新生代对象朝生暮死,存活率通常低于15%。复制算法只搬运活对象,天然避免内存碎片,而且效率高——不用遍历整个空间做清除。但代价是必须预留一块空闲区(比如 Eden:S0:S1 = 8:1:1),空间利用率只有理论的一半。
- 如果把复制算法硬塞进老年代,频繁复制大对象会严重拖慢吞吐量,
Full GC时间飙升 -
Survivor区太小会导致Minor GC后大量对象直接晋升到老年代,触发提前Full GC -
MaxTenuringThreshold设太高,短命对象在 Survivor 区反复复制,浪费 CPU
标记-清除在老年代的致命问题:碎片化 + 分配失败
它只标记再统一清除,不移动对象。久而久之,空闲内存被切成无数小块。当一个大对象(比如 byte[8MB] 数组)申请内存时,即使总空闲够,也因找不到连续空间而触发 Concurrent Mode Failure 或直接 Full GC。
-
Serial Old和Parallel Old默认用标记-整理,就是为了避免这个问题 -
CMS是唯一长期用标记-清除的老年代收集器,靠ConcMarkSweepGC参数开启,但它必须配合-XX:CMSInitiatingOccupancyFraction提前启动回收,否则极易失败 - 一旦 CMS 失败退化成
Serial Old,应用会卡住几秒甚至几十秒——这就是为什么 JDK9 后直接移除了 CMS
标记-整理不是万能解药:移动成本与并发难度
它要移动所有存活对象并更新引用,意味着 STW 时间比标记-清除长。所以它适合对吞吐量要求高、能接受稍长停顿的场景(如 Parallel GC),不适合低延迟场景。
立即学习“Java免费学习笔记(深入)”;
- 移动对象时,所有指向它的引用都得重写,JVM 要扫描整个堆或使用记忆集(
Remembered Set),开销不小 -
G1的混合回收(Mixed GC)虽叫“标记-整理”,实际是把回收集(Collection Set)里的分区整体复制到新分区,本质还是复制,只是粒度更细 - 别指望调个
-XX:+UseMarkSweepGC就能解决问题——这个参数在 JDK8 已废弃,JDK9+ 直接报错
真正容易被忽略的是:算法本身没有好坏,但收集器实现决定了你能不能控制它。比如 Shenandoah 和 ZGC 的并发整理,根本不在传统三算法框架里,它们靠硬件特性(如内存保护页、着色指针)绕开了移动对象时的全局停顿。想调优,先看清楚你用的是哪个收集器,而不是背算法名字。









