jvm内存碎片的本质是空闲内存不连续,导致大对象无法分配;可通过gc日志中的promotion failed、concurrent mode failure等信号识别,结合jstat、jmap与mat分析对象分布及自由块大小,并依据cms、g1等收集器特性定位根源。

Java 中分析 JVM 内存碎片的产生原因,关键不是看“用了多少内存”,而是看“空闲内存是否连得上”。碎片问题常被误判为内存不足,实则堆里明明有空间,却因零散分布而无法分配大对象。下面从信号识别、工具验证、根源定位三方面说清楚怎么查。
看 GC 日志里的“失效回收”信号
真正由碎片引发的问题,GC 日志会留下明显痕迹:
- Promotion failed:年轻代对象晋升老年代时失败,说明老年代虽有空闲,但找不到连续区域容纳该对象
- Concurrent mode failure(CMS)或 To-space overflow(G1):都是晋升/转移阶段因空间不连续导致的失败
- Full GC 前后 OU(Old Used)变化极小:比如 GC 前 76%,GC 后 74%,说明垃圾确实清了,但空间没腾出来——大概率是碎片卡住了压缩
- 日志中出现 “Fragmentation” 字样 或显示 “largest free chunk” 明显偏小(如仅 2MB,而应用常需 8MB 缓存块)
用 jstat 和 jmap 辅助交叉验证
单靠日志还不够直观,需要数据支撑:
-
jstat -gc
:持续观察 OU 持续高位、OC 未满、FGC 频次上升,且 YGCT 不降反升,说明年轻代在反复 GC 却推不进老年代——可能被碎片堵住入口 -
jmap -histo:live
:重点看大小集中在 64KB–2MB 的对象数量。若大量中等尺寸对象长期存活(如缓存容器、序列化缓冲),它们就像“楔子”钉在老年代中间,把大块空闲切成小洞 -
jmap -dump:format=b,file=heap.hprof
:用 MAT 打开后,查看 Dominator Tree,找谁持有大量中小对象;再用 “Histogram → Group by Class”,看是否有非预期的 byte[]、char[] 或 HashMap$Node 实例堆积
结合 GC 算法特性反推碎片来源
不同收集器对碎片的“容忍度”和“制造力”差异很大,直接关联配置与行为:
- CMS 收集器:标记-清除不移动对象,天然易碎。若启用了 -XX:+UseCMSCompactAtFullCollection 却仍频繁 Full GC,说明碎片已严重到压缩都难凑效;可加 -XX:+PrintFLSStatistics=2(JDK 8 及以前)直出自由列表统计,看到空闲块数量暴增、平均大小骤降就是铁证
- G1 收集器:虽有混合回收压缩能力,但若 -XX:G1HeapRegionSize 设得过大(如 4MB),小对象分配被迫挤进大 Region,内部浪费加剧;若 Mixed GC 触发频率低、老年代 Region 碎片化高,可调小 -XX:G1MixedGCCountTarget 加快整理节奏
- Parallel Old:标记-整理类,本应低碎片,但如果 Survivor 区太小(-XX:SurvivorRatio 过大),大量对象“逃逸”进老年代,也会加速碎片积累
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











