谈到CMS与G1在内存碎片处理上的差异,许多人第一反应是“CMS碎片多,G1碎片少”。这一说法没错,但并未触及本质。两者真正的分水岭,不在于“是否会产生碎片”,而在于一个更核心的问题:碎片是否会立即阻塞内存分配的通道。CMS由于算法和内存布局的设计,碎片一旦出现,分配就可能直接失败;G1则借助分区和整理机制,将碎片限制在局部区域,避免其影响全局分配。

算法底层:标记-清除 vs 标记-整理
CMS底层采用标记-清除算法——仅回收垃圾,不移动存活对象。老年代是一整块连续地址空间,回收后空闲内存变为零散的小块。此时若需分配较大对象(如数组或缓存块),必须找到一段连续空闲区域。一旦找不到,即使总空闲空间尚余不少,也会直接触发 promotion failure 或 concurrent mode failure,进而降级为Serial Old全堆压缩GC——代价极高,停顿时间极长。
G1则走另一条路:标记-整理(实为局部复制)。在筛选回收阶段,它将选中Region中的存活对象复制到其他Region,原Region直接整体释放。这一动作本身就是空间整理——释放后的Region干净整洁,不会出现“明明有空位却无法分配”的假性耗尽问题。
内存组织:连续段 vs 固定大小Region
CMS的老年代是单一连续的物理段,碎片随运行时间不断累积,且无法隔离——碎片化严重时只能硬扛。而G1将堆划分为固定大小的Region(默认1–32MB),每个Region相互独立。分配对象时,只要系统中还有空闲Region(或多个Region组合后可满足需求),就能完成分配,不依赖连续虚拟地址空间。碎片被限制在单个Region内部,不影响跨Region的分配。即使某些Region剩余少量空间,只要整体空闲Region数量足够,分配就不会中断。
应对机制:被动压缩 vs 主动收益驱动回收
CMS日常对碎片几乎不做处理。只有等到Concurrent Mode Failure或Full GC发生时,才可能借助UseCMSCompactAtFullCollection参数触发一次压缩——但这是高停顿的补救措施,且仅出现在Full GC场景。简言之,平时不管理,直到崩溃才收拾。
G1的Mixed GC本身就是整理过程:它根据垃圾密度和回收收益动态选择待回收的Region,回收的同时完成复制和腾空。也就是说,碎片控制是常态化、渐进式的——并非等出问题再补救,而是一边使用一边整理,细水长流。
关键信号:失败表现完全不同
从日志来看,两者的失败信号也截然不同:
- CMS出现碎片问题时,日志典型特征是一串concurrent mode failure后紧跟着Abort Preclean或CMSCollector: abort,然后降级为Serial Old——表明系统已无法支撑。
- G1出现碎片压力时,日志会显示to-space exhausted,表示目标Region内没有整块空间容纳复制对象;或Mixed GC中Free CSet远小于Total CSet——说明它正在东拼西凑Region,但收效甚微,性能开始下降。
理解这些信号,才能在实际调优时对症下药,而不是看到“碎片”二字就盲目切换GC。
