CMS产生内存碎片的根本原因是采用标记-清除算法且不进行内存整理,即清除阶段仅释放死亡对象空间而不移动存活对象,导致空闲空间离散、不连续。

CMS收集器产生内存碎片的根本原因在于它采用标记-清除(Mark-Sweep)算法,不进行内存整理——清除阶段只是回收死亡对象占用的空间,但不会移动存活对象,导致剩余空间分散、不连续。
为什么标记-清除必然带来碎片
标记-清除分两步:先遍历并标记所有存活对象,再遍历堆内存,把未被标记的对象所占空间直接释放。这个过程不涉及对象搬迁,因此存活对象仍留在原地,中间空出的“洞”彼此隔离。随着多次GC,老年代逐渐布满细小、离散的空闲块,形成大量无法利用的小碎片。
例如:老年代还剩 200MB 空间,但最大连续空闲块只有 1MB;而新晋升的大对象(如 5MB 的 byte[])无法分配,只能触发 Full GC——哪怕总空闲远大于需求。
CMS默认不压缩,靠Full GC时补救
CMS本身不提供并发整理能力,所以它把内存整理任务推迟到不可避免的 Full GC 阶段,并通过以下两个参数控制整理行为:
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
立即学习“Java免费学习笔记(深入)”;
- -XX:+UseCMSCompactAtFullCollection:启用 Full GC 后的内存压缩(即整理),将存活对象向一端靠拢,消除碎片。该参数默认开启(JDK 6u23+)。
- -XX:CMSFullGCsBeforeCompaction=n:设定每执行 n 次 Full GC 后才做一次压缩。默认值为 0,表示每次 Full GC 都压缩;设为 10,则第 1、2…9 次 Full GC 不整理,第 10 次才整理。
注意:整理过程是 Stop-The-World 的,会显著延长 Full GC 停顿时间。因此调大 CMSFullGCsBeforeCompaction 可减少停顿频率,但会加剧碎片累积风险。
更实用的碎片预防配置
与其依赖 Full GC 整理,不如从源头降低碎片发生概率:
- -XX:CMSInitiatingOccupancyFraction=70:让 CMS 在老年代使用率达 70% 时就启动,预留足够空间容纳浮动垃圾和避免因空间不足被迫退化为 Serial Old。
- -XX:+UseCMSInitiatingOccupancyOnly:禁用 JVM 动态调整触发阈值,确保始终按设定值触发,提升可预测性。
- 搭配 ParNew 回收新生代:减少大对象直接进入老年代的机会;同时合理设置 -Xmn 和 -XX:SurvivorRatio,避免过早晋升。
重要提醒:CMS 已淘汰,建议迁移
CMS 自 JDK 9 起被标记为废弃(JEP 291),JDK 14 正式移除。当前主流版本(JDK 17/21)应优先选用 G1、ZGC 或 Shenandoah。它们均内置压缩或区域化管理机制,天然缓解碎片问题,且支持并发整理。

















