标记-清除算法直接导致内存碎片化,因其仅标记存活对象并回收死亡对象空间,不移动存活对象,使空闲内存散落为大小不一、彼此隔离的小块,无法满足大对象连续分配需求。

标记-清除算法本身不解决内存碎片化,而是直接导致碎片化。
为什么标记-清除会产生碎片
它只做两件事:标记所有存活对象,然后回收未被标记的垃圾对象所占空间。过程中完全不移动任何存活对象。释放后的空闲内存散落在存活对象之间,形成大量大小不一、彼此隔离的小块——这就是内存碎片。
比如堆中原本分布着 A、B、C 三个存活对象,中间夹着几块被回收的空闲区。下次想分配一个比任意一块都大的对象(如 1MB 数组),即使总空闲量有 2MB,也会因没有连续 1MB 空间而失败。
它无法解决碎片,但可被其他机制“补救”
单独使用标记-清除时,碎片会持续累积。实际 JVM 中,它常配合以下方式缓解问题:
-
搭配整理动作:CMS 收集器默认用标记-清除清理老年代,但可通过
-XX:+UseCMSCompactAtFullCollection在 Full GC 时额外触发一次压缩(即标记-整理),把存活对象挪到一起 - 限定使用范围:现代收集器如 G1、ZGC 已基本弃用纯标记-清除;它主要保留在 CMS 的并发清理阶段或某些低延迟场景的局部回收中,避免全局影响
- 依赖空闲列表管理:JVM 用“空闲列表(Free List)”记录所有可用小块,分配时尝试拼凑或查找合适尺寸——但这只是应对,不是消除
真正解决碎片的是标记-整理算法
如果目标是消除碎片,JVM 会切换到标记-整理(Mark-Compact):
- 标记阶段同标记-清除:从 GC Roots 出发,标记所有可达对象
- 整理阶段是关键:将所有已标记的存活对象,按地址顺序紧凑地迁移到堆起始端(如低地址侧)
- 清除阶段变得极轻量:只需把分配指针设在最后存活对象之后,后续对象顺序分配,天然连续
这个“物理重排”过程让碎片消失,换来一块完整可用空间,专为大对象分配而生。

















