G1中大对象超Region一半即入Humongous区,仅Full GC可回收,高频分配致老年代迅速填满并触发Full GC;可通过GC日志、jstat及调整Region大小或代码层规避来解决。

大对象直接进老年代,是 G1 频繁 Full GC 的常见导火索
当对象大小超过 HeapRegionSize 的一半,G1 就把它标记为 Humongous 对象,并**直接分配到老年代的 Humongous Region 中**。这类 Region 无法被 Young GC 或 Mixed GC 回收,只能等 Full GC 才能清理——一旦高频创建(比如每次请求 new 一个 2MB 的 byte[],而 G1HeapRegionSize=4M),老年代会迅速填满,触发 to-space exhausted 或 concurrent mode failure,最终退化为 Serial Full GC。
怎么判断是不是 Humongous 分配导致的问题
打开 GC 日志(加 -Xlog:gc*,gc+humongous*=debug),重点看三类线索:
- 日志中频繁出现
Humongous allocation或Attempt to allocate humongous object - Full GC 前紧跟着
to-space overflow或Concurrent collection failed -
jstat -gc显示OU(老年代已用)持续上涨,但OC(老年代容量)不变,说明不是内存泄漏,而是 Region 被长期占用
调整 G1HeapRegionSize 不是万能解,得看堆大小和对象分布
Region 太小(如默认 1MB)会让本可放入单个 Region 的对象被拆成多个 Humongous Region,浪费空间且加剧碎片;Region 太大(如 32MB)又会导致单次回收成本高、晋升更激进。关键是要让典型大对象「刚好」能放进一个 Region:
- 堆 ≤ 4GB:默认
G1HeapRegionSize=1M,若常分配 1.5MB 数组,就该设为-XX:G1HeapRegionSize=2M - 堆 8–16GB:建议
2M或4M,避免 3MB 左右的对象跨 Region - 堆 ≥ 32GB:可设
8M~16M,但需同步检查SurvivorRatio和G1MixedGCCountTarget,防止年轻代过小
注意:G1HeapRegionSize 必须是 2 的幂(如 1048576 = 1M),且 JVM 启动后不可更改。
比调参更有效的做法:从代码层规避 Humongous 分配
参数只是兜底,根治要落在分配行为本身。以下方式实测降低 Humongous 分配率达 80% 以上:
- 用
ArrayList替代预分配大数组,尤其在不确定长度时(new ArrayList(initialCapacity)比new byte[10*1024*1024]更可控) - 对固定用途的大缓冲区(如文件上传、JSON 解析),改用对象池(
GenericObjectPool或Recycler),复用而非新建 - 批量处理场景下,拆分大任务:把一次处理 100MB 数据,改为流式分块读取 + 即时处理,避免瞬时大对象压垮 Region
- 检查第三方库(如 Jackson、Netty)是否开启大对象优化选项,例如 Jackson 的
JsonFactory.Feature.USE_THREAD_LOCAL_FOR_BUFFER_RECYCLING
真正容易被忽略的是:Humongous Region 一旦分配,哪怕里面只用了 1KB,整个 Region(比如 4MB)就无法被其他对象复用,且不会参与任何并发回收阶段——它就像一块“钉子户”,直到 Full GC 才挪走。


















