老年代不用标记清除算法是因为会产生内存碎片,导致大对象无法分配而频繁Full GC;传统收集器如Serial Old、Parallel Old采用标记整理算法通过压缩存活对象来避免碎片,但STW时间长。

老年代为什么不用标记清除算法
因为标记清除后会产生大量不连续的空闲内存块,而老年代主要存放大对象和长期存活对象,频繁分配大对象时容易触发 Full GC 甚至直接 java.lang.OutOfMemoryError: Java heap space。
比如一个 2MB 的对象要分配,但堆里只剩若干 512KB 的碎片——它就放不下,哪怕总空闲内存远大于 2MB。
常见错误现象:GC logs 显示老年代空间使用率不高(如 60%),却频繁 Full GC;或者 Parallel GC 下老年代回收后 used 没降多少,说明碎片严重。
- 标记清除适合新生代:对象朝生夕灭,回收后空间很快被新对象填满,碎片影响小
- 老年代对象存活率高,长期积累碎片不可逆,必须主动整理
-
G1和ZGC是例外,它们用不同机制(分区 + 复制 / 读屏障)规避碎片,但传统Serial Old、Parallel Old都依赖标记整理
标记整理算法怎么避免碎片
核心是把所有存活对象往一端挪,然后直接清理边界外的内存——效果等价于“压缩”,结果是一整块连续空闲空间。
立即学习“Java免费学习笔记(深入)”;
这不是简单移动:JVM 要更新所有指向这些对象的引用(包括栈帧里的局部变量、静态字段、其他对象的字段),所以停顿时间比标记清除长。
- 移动过程需暂停所有应用线程(
STW),这是老年代 GC 停顿长的主因之一 - 整理方向通常是从低地址向高地址移动(
Serial Old默认),也可反向,取决于实现 - 如果老年代空间本身已极度紧张(比如只剩 5% 空闲),整理可能失败,退化为标记清除(部分 JVM 实现会这样保底)
大对象(Large Object)如何影响老年代策略
HotSpot 中,超过 -XX:PretenureSizeThreshold 的对象直接进老年代(绕过新生代),这类对象天然需要大块连续内存。
如果老年代用标记清除,每次分配大对象都要遍历空闲链表找足够大的块,效率低且易失败;而标记整理后,只需检查最高空闲地址是否满足大小即可。
-
-XX:PretenureSizeThreshold默认为 0(禁用),设为 1M 并不意味着所有 >1MB 的对象都进老年代——还要看当前新生代剩余空间是否够存下它 - 使用
G1时,大对象存放在Humongous Region,本质是连续的多个 region,仍依赖底层内存连续性 - 频繁分配大对象又没及时释放,会快速撑爆老年代,此时标记整理也救不了——得靠调优或代码改写
不同垃圾收集器的老年代算法差异
不是所有 GC 都用标记整理:它是传统“吞吐优先”收集器的选择,而低延迟目标会换思路。
-
Serial Old和Parallel Old:严格使用标记-整理(Mark-Compact) -
CMS:老年代用标记-清除(Mark-Sweep),靠-XX:+UseCMSCompactAtFullCollection在 Full GC 后强制整理一次,但默认不开启,且整理过程 STW 很长 -
G1:没有独立的老年代概念,用增量式复制(Evacuation)+ 分区,天然防碎片 -
ZGC/Shenandoah:并发整理,通过读屏障和转发指针实现,STW 时间与堆大小无关
选 GC 时,如果业务有稳定大对象分配模式(如缓存批量加载、报表导出),又不能接受 G1/ZGC 的额外内存开销或 JDK 版本限制,那 Parallel Old 的标记整理仍是更稳的选择——只是得接受它偶尔较长的 STW。
真正容易被忽略的是:标记整理解决的是“能放得下”,不是“放得快”。如果对象分配速率持续超过 GC 回收能力,再连续的内存也会迅速耗尽。








