cms老年代不压缩导致碎片化引发晋升失败和长停顿full gc,解决核心是控制碎片:调低cms触发阈值、启用自动压缩、优化对象分配与年轻代大小,并在低峰期可控触发带压缩的full gc。

CMS本身不压缩老年代,所以“变量无法压缩”不是变量的问题,而是老年代空间碎片化导致晋升失败(promotion failure),进而触发STW时间极长的Full GC。解决核心不是让变量可压缩,而是控制碎片、延缓或避免因碎片引发的Full GC。
启用CMS自动碎片整理
CMS默认在每次Full GC时执行内存压缩(即Mark-Sweep-Compact),但这个过程是单线程、完全STW的,耗时严重。可通过以下参数显式控制:
- -XX:+UseCMSCompactAtFullCollection:开启Full GC后压缩(默认已开启)
- -XX:CMSFullGCsBeforeCompaction=N:设为正整数(如5或10),表示每N次Full GC才执行一次压缩,其余Full GC跳过压缩——这能减少停顿,但会加速碎片累积,需权衡
提前触发并发回收,缓解碎片恶化
CMS的并发标记-清除阶段虽不压缩,但会合并相邻空闲块(coalesce),提升大对象分配成功率。关键在于让并发GC更早启动:
- 调低触发阈值:-XX:CMSInitiatingOccupancyFraction=70(默认约92%),让老年代使用率达70%就启动CMS,避免等到空间濒临耗尽才回收
- 配合-XX:+UseCMSInitiatingOccupancyOnly,防止JVM动态调整该阈值
从应用侧降低碎片生成压力
碎片本质源于大量中等生命周期对象反复晋升又部分存活,造成老年代空间被切割。可针对性优化:
- 减少大对象直接进入老年代:检查是否误用
new byte[8MB]等,考虑池化或拆分 - 调大年轻代(尤其是Survivor区):用
-XX:SurvivorRatio=6或增大-Xmn,让更多对象在YGC中被回收,降低晋升频率 - 对HBase等典型场景,启用
MemStore-LocalAllocationBuffer(LAB),让MemStore分配集中在小块连续内存,显著减少老年代碎片源
兜底策略:可控时机的强制压缩
若业务有低峰期(如凌晨),可主动触发一次带压缩的Full GC,重置碎片状态:
- 在维护窗口调用
System.gc()(需确保-XX:+UseConcMarkSweepGC和-XX:+UseCMSCompactAtFullCollection已启用) - 或通过JMX发送
gc()操作,比代码调用更可控 - 注意:禁止在高峰期或响应敏感路径中调用,否则等于主动制造长停顿











