g1收集器将超过region容量50%的对象标记为humongous并强制分配连续region;其识别依据是对象原始数据大小,分配跳过年轻代直接在老年代寻找连续空闲region,首个为hs、后续为hc;因需物理连续性,碎片易致full gc;回收仅限mixed gc且要求并发标记完成,jdk8u40后支持单region回收但受限于对象可达性;调优需结合日志分析典型大小、预留空间及代码层优化。

G1 收集器对 Humongous 对象的处理不是简单地“扔进老年代”,而是一套受严格约束的连续内存分配与回收机制。核心在于:对象大小超过单个 Region 容量的 50% 时,G1 就将其标记为 Humongous,并强制分配一组物理连续的 Region。
Humongous 对象怎么被识别和分配
判断依据是对象原始数据占用的字节数,比如 byte[] 的长度、String 内部 char[] 的容量,而不是引用本身。
- Region 大小为 2MB → 超过 1MB 的对象即触发 Humongous 分配
- Region 大小为 4MB → 门槛升至 2MB;8MB Region 对应 4MB 门槛
- 分配跳过 Eden 和 Survivor,直接在老年代区域中寻找足够数量的物理连续空闲 Region
- 首个 Region 标记为 Humongous Start,后续为 Humongous Continuation
- 找不到足够连续空闲段(如需 3 个连续 Region,最大空闲段只有 2 个),G1 会立即触发 Full GC
为什么必须连续分配
Java 对象头 + 实际数据必须位于连续地址空间,JVM 无法通过单一指针访问跨 Region 的对象。G1 没有设计跨 Region 寻址机制,只能靠物理连续性保证语义正确。
- 这导致 Humongous 分配对堆碎片极其敏感:总空闲 Region 数够,但不连续,照样失败
- 一个 2.1MB 对象在 2MB Region 下需占 2 个连续 Region(4MB),空间浪费近一半
- 回收时这组 Region 必须整体释放,中间插入其他对象会阻断复用路径
Humongous 对象何时能被回收
它不参与 Young GC,回收窗口非常有限,仅在 Mixed GC 阶段有条件纳入回收集合。
- 前提:并发标记周期已完整结束,且该 Humongous Region 所在的老年代区域被判定具备回收价值
- JDK 8u40 起支持单 Region 级回收(不再强求整组释放),但前提是该 Region 内所有对象都不可达
- 实际中常因残留引用或 SATB 标记延迟,导致短命大对象也无法及时释放
- 长期被持有引用,或连续空间持续紧张,极易退化为 Full GC
调优的关键落点
不能只看有没有大对象,要确认它是否构成 GC 瓶颈。
- 用 -Xlog:gc+humongous*=debug 日志统计典型大小,反向匹配 Region 阈值
- 常见易踩坑对象:Protobuf 反序列化结果、大 byte[]、长文本 String、ArrayList 扩容后数组
- 配合 -XX:G1ReservePercent=15 预留空间缓解连续 Region 不足问题
- 代码层更有效:用 ArrayList 替代预分配大数组、引入对象池复用缓冲区、流式分块处理代替一次性加载











