堆内存碎片化本质是空闲空间不连续,导致大对象无法分配,进而引发频繁gc甚至oom;标记-清除类(如cms)不处理碎片、只留隐患;复制类(如serial、g1年轻代)天然无碎片但空间减半;标记-整理类(如g1混合回收、zgc、shenandoah)边回收边压缩,兼顾空间利用率与低碎片。

堆内存碎片化本质是空闲空间不连续,导致大对象无法分配,进而引发频繁 GC 甚至 OOM。不同垃圾回收算法不是“能不能处理碎片”,而是“怎么处理”——有的回避、有的预防、有的主动整理。
标记-清除类:不处理碎片,只留隐患
这类算法(如 CMS)分两步:先标记存活对象,再清除未标记区域。它不做对象移动,所以停顿短,但会留下大量零散空闲块。
- 典型表现:总空闲内存充足,却因找不到连续 4MB 空间而分配失败
- 后果是被迫触发 Full GC,甚至在 JDK 9 后被官方弃用
- 适合场景极少——仅限对延迟极度敏感、且堆不大、对象生命周期高度可控的老系统
复制类:天然无碎片,靠空间换连续性
Serial、Parallel Scavenge 和 G1 的年轻代都用复制算法。它把内存划成 Eden + Survivor 或多个 Region,GC 时只把存活对象复制到新区域,原区域整块清空。
- 每次回收后目标区始终紧凑,完全规避外部碎片
- 代价是必须预留备用空间(比如 Eden:Survivor = 8:1),实际可用堆容量打折扣
- 当对象存活率高时(如长生命周期缓存),复制开销陡增,效率明显下降
标记-整理类:边回收边压缩,兼顾空间与连续
G1 混合回收、Parallel Old、ZGC 和 Shenandoah 都属于这一类。它们不只是清理,还主动重排内存布局。
- G1 在混合回收阶段挑选部分 Region,将其中存活对象复制到其他 Region,腾出整块干净空间
- Parallel Old 直接将老年代存活对象向一端滑动压缩,合并空闲区
- ZGC 和 Shenandoah 更进一步,在应用线程运行的同时并发完成对象移动和引用更新,实现低停顿+低碎片
- 缺点是需额外处理指针重定向,带来一定 CPU 开销,但换来的是高空间利用率和稳定分配能力
选型关键看实际约束
没有“最好”的回收器,只有“最合适”的组合:
- 延迟敏感、堆 ≤16GB、可接受少量 CPU 开销 → ZGC 或 Shenandoah
- 吞吐优先、堆较大(32GB+)、能容忍 100ms 级停顿 → G1
- 老旧系统、堆小(
- 新项目直接排除 CMS,它不解决碎片,反而因碎片加剧 GC 压力











