混合回收阶段oom本质是碎片削弱分配能力:mixed gc选择性回收不压缩全堆,释放内存离散,大对象因缺连续空间分配失败,触发full gc仍无效后抛出java.lang.outofmemoryerror。

垃圾回收器混合回收阶段堆内存碎片化溢出,本质不是“碎片直接导致OOM”,而是碎片削弱了内存分配能力,叠加混合回收(Mixed GC)的特定行为,最终在对象分配失败时触发 java.lang.OutOfMemoryError: Java heap space。
混合回收阶段为何加剧碎片敏感性
混合回收(如G1、ZGC的并发标记后阶段)会同时清理部分老年代区域和所有年轻代。它不保证对整个堆做压缩,而是选择性地回收收益高的Region(G1)或使用读屏障+重映射(ZGC),但不主动整理全部存活对象。这意味着:
- 回收后释放的内存呈离散小块,而非连续大块;
- 大对象(≥Region大小或大于ZGC的Large Page阈值)无法找到足够连续空间分配;
- 即使总空闲内存充足,也会因缺乏连续空间而分配失败。
碎片化溢出的底层触发链
真正抛出OOM前,JVM经历以下不可逆步骤:
- 应用线程尝试为新对象(尤其是大数组、ByteBuffer、长字符串等)分配内存;
- 内存分配器(如G1的Allocator或ZGC的Page Allocator)扫描空闲内存链表,发现无单块满足请求大小;
- 触发一次Full GC(G1中为退化Full GC,ZGC中为STW的Final Mark + Relocate)作为最后尝试;
- 若Full GC后仍无法腾出足够连续空间(例如大量中等大小对象长期存活、跨Region引用阻塞整理),JVM放弃分配并抛出OOM。
哪些代码/配置会放大该问题
以下情况会显著提高碎片化溢出概率:
- 频繁分配生命周期不一的大对象:如每次HTTP请求生成数MB的JSON响应体,又未复用缓冲区;
- G1中Region尺寸设置不合理:默认2048个Region,若堆为8GB,则每Region约4MB;若业务常见对象为6MB,必然跨Region,加剧碎片感知;
-
禁用或弱化压缩行为:如G1未启用
-XX:+G1UseAdaptiveIHOP或ZGC未开启-XX:+ZUncommit,导致空闲页无法及时归还系统再整合; - 长期持有对象引用阻碍移动:JNI全局引用、栈上强引用、未清理的WeakHashMap Key等,使GC无法 relocate 对象,固化碎片格局。
验证与定位的关键动作
不能只看GC日志中的“mixed”字样,要抓三个证据:
- 启用
-Xlog:gc*,gc+heap=debug,确认OOM前一次Mixed GC是否伴随大量“Humongous allocation failed”; - 用
jstat -gc <pid></pid>观察EU(Eden使用率)和OU(Old使用率)持续高位,但OC(Old容量)未达Max,说明是碎片而非总量不足; - 生成Heap Dump后用Eclipse MAT打开,按“Histogram → Group by Package”,重点查看
[B(byte[])、[C(char[])、java.nio.DirectByteBuffer等大数组类的实例数与总占用,再用“Merge Shortest Paths to GC Roots”反查谁长期持有着它们。











