G1比CMS更少因内存碎片分配失败,因其将堆划分为固定大小Region,按收益回收,不依赖连续空间;而CMS老年代连续易碎片化,导致频繁失败和退化。

为什么G1比CMS更少因内存碎片分配失败
G1在设计上就避免了CMS那种“全堆并发标记+老年代碎片累积”的死循环。CMS的老年代是连续空间,大对象只能找连续空闲块;一旦碎片化,Concurrent Mode Failure或promotion failure就频繁触发,最终退化为Serial Old单线程GC——卡顿明显。G1把堆切成固定大小Region(默认1~32MB),回收时按收益排序选若干Region清理,天然不依赖连续空间。只要总空闲Region够用,哪怕分散,也能分配新对象。
但注意:G1不是完全免疫碎片。当大量中等对象(比如4–8MB)长期存活,又没被完整回收的Region塞满,就会出现“可用Region数量足、但每个都剩一点点空闲”的假性碎片——这时Allocation Failure仍可能触发Mixed GC,甚至退化到Full GC。
- 关键区别不在“有没有碎片”,而在“碎片是否阻断分配”:CMS一碎就卡,G1靠Region粒度和复制算法绕过
- G1的
InitiatingHeapOccupancyPercent(默认45%)设太低,会过早启动Mixed GC,反而增加开销;设太高,则碎片积累到临界点才反应,风险上移 - CMS下
UseCMSCompactAtFullCollection能强制压缩,但只在Full GC时生效,而G1的Full GC本身就是压缩式(mark-compact),无需额外开关
如何判断当前是G1碎片问题而非单纯内存不足
看GC日志里Allocation Failure后的动作:如果紧接着是Mixed GC且耗时陡增(尤其Other阶段超100ms),同时Free CSet(待回收Region数)远小于Total CSet(实际选入的Region数),说明G1在拼命凑Region却凑不够理想收益——这是碎片信号。而纯内存不足的表现是:每次Young GC后老年代占用持续攀升,Old Gen使用率很快逼近MaxGCPauseMillis容忍上限,但Mixed GC频率并不异常。
- 用
jstat -gc <pid></pid>观察MGCC(Mixed GC次数)和CCSC(Concurrent Cycle次数)比值:若MGCC远高于CCSC,大概率是碎片驱动的被动回收 -
PrintGCDetails日志中搜索to-space exhausted——这是G1明确告诉你“想复制对象过去,但目标Region没整块空间了”,比OOM更早暴露碎片问题 - CMS下对应现象是
concurrent mode failure后紧跟(concurrent mode failure)括号标注,且后续GC日志出现Abort Preclean或CMSCollector: abort
G1碎片优化的三个实操参数调整点
别迷信调大MaxGCPauseMillis。它只是目标,不是保证;设太高会让G1减少回收Region数,反而加剧碎片。真正该动的是这三项:
-
MaxGCPauseMillis保持默认200ms或略降(如150ms),逼G1多选Region,提升空间利用率 -
G1HeapRegionSize根据对象大小分布调整:如果应用大量6MB对象,设为8MB能让单个Region刚好装下,减少跨Region引用和内部碎片;但设太大(如32MB)会导致小对象浪费空间,反而抬高碎片率 -
G1ReservePercent(默认10%)是预留缓冲区,防Mixed GC过程中新对象涌入导致失败;生产环境可提到15–20%,尤其写放大明显的场景(如频繁String拼接)
改完必须验证:jmap -heap <pid></pid>看Heap Usage里used和capacity差值是否稳定,再配合jstat -gc <pid> 1s</pid>确认MGCC频率是否回落。
CMS迁移到G1时最容易忽略的兼容陷阱
CMS可以和ParNew搭配实现低延迟,但G1自己就是分代+区域混合模型,-XX:+UseParNewGC在JDK9+已废弃,硬加会报错。更隐蔽的是JVM参数冲突:-XX:CMSInitiatingOccupancyFraction对G1完全无效,但若配置残留,某些旧版监控脚本可能误读为“CMS启用”,导致告警逻辑错乱。
- JDK8u40之后G1默认开启
String Deduplication(需-XX:+UseStringDeduplication显式打开),CMS不支持;迁移后若发现String对象GC行为异常,先检查这个开关 - CMS下
SurvivorRatio影响年轻代布局,G1里它被忽略——G1用动态计算的TargetSurvivorRatio(默认50%)管理Survivor空间,硬设SurvivorRatio无意义 - GC日志格式完全不同:CMS用
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps,G1必须加-Xlog:gc*:file=<path>:time,uptime,level,tags</path>(JDK10+)或-XX:+PrintGCDetails(JDK8)才能获取Region级信息
碎片问题从来不是单点参数能根治的,它暴露的是对象生命周期与GC策略的错配。G1给了更多腾挪空间,但也要求你更懂自己应用的对象尺寸分布和晋升节奏——日志里那行to-space exhausted,其实是JVM在提醒你:该看看对象到底怎么活下来的了。

















