G1:现代默认垃圾收集器#
版本信息
- 全称:Garbage First(「优先回收垃圾最多的区域」)
- 默认:JDK 9 起默认(JEP 248);JDK 7u4 产品化引入。当前 11/17/21/25 LTS 均默认 G1
- MXBean 名:
G1 Young Generation/G1 Old Generation(JDK 21 另有G1 Concurrent GC)——本机实测 - 关键 JEP:248 默认(9)|307 并行 Full GC(10)|344 可中止混合回收(12)|345 NUMA 感知(14)|346 及时归还内存(12)
从 JDK 9 开始,什么都不配,默认就是 G1。它是当前 Java 通用场景的「安全默认值」:堆切成区域(region)、优先回收垃圾最多的区域,靠停顿预测模型把单次回收控制在目标内,且因为是复制式回收、天然无碎片——正好补上了 CMS 的两个死穴。本页讲清它的原理、参数和「为什么多数时候你该信任它」。
设计目标:停顿可控 + 无碎片#
G1(Garbage First)的设计目标是在较大堆上实现可预测的停顿,同时避免碎片化:
- 堆 region 化:把堆切成大量大小相等的 region(默认 1–32MB),每个 region 动态充当 Eden / Survivor / Old / Humongous。年轻代不再是连续的一大块,而是「一组 region」的逻辑集合。
- Garbage First:G1 跟踪每个 region 的存活对象量(即「垃圾比例」),优先回收垃圾最多的 region——用最少的回收工作释放最多的空间。
- 停顿预测模型:根据
-XX:MaxGCPauseMillis目标和历史耗时数据,预测回收每个 region 的成本,挑出「能在目标停顿内完成」的 region 组成回收集(CSet)。这是 G1 停顿可控的核心。 - 复制式回收 → 无碎片:把 CSet 里的存活对象复制到空 region、原 region 整个释放。因为总是复制整理,不存在 CMS 那样的碎片化。
工作原理:Young GC + 并发标记 + Mixed GC#
graph TD
A["分配压力"] --> YG["Young GC(STW)<br/>回收年轻代 region"]
YG -->|"堆占用达 IHOP(默认 45%)"| CM["并发标记<br/>统计各 region 垃圾比例(SATB)"]
CM --> MX["Mixed GC(STW)<br/>年轻代 + 垃圾多的老年代 region"]
MX --> YG
MX -->|"跟不上分配"| FG["Full GC(兜底)<br/>JDK 11 起并行"]
style YG fill:#bbdefb
style MX fill:#c8e6c9
style FG fill:#ffcdd2
- Young GC(STW,频繁):Eden region 用尽时触发,复制存活对象到 Survivor / Old region,回收年轻代。这是日常、短停顿的回收。
- 并发标记(与应用并发):当堆占用达 IHOP(
InitiatingHeapOccupancyPercent,默认 45%)启动,用 SATB(开始时拍快照 + 写屏障记录变更)统计每个老年代 region 的存活对象量,为下一步挑 region 做准备。 - Mixed GC(STW):在停顿目标内,回收全部年轻代 + 若干垃圾比例高的老年代 region。多次 Mixed GC 逐步清理老年代,而非一次性整堆。
- Full GC(兜底):当 Mixed GC 跟不上分配速率时触发。JDK 10 前 Full GC 是单线程(Serial),JEP 307(JDK 10)改为并行(JDK 11 LTS 承接),worst-case 停顿大幅缩短。
graph LR
subgraph H["G1 堆 = 多个 region"]
E["Eden region"] --- S["Survivor region"]
S --- O["Old region"]
O --- HU["Humongous region<br/>(大对象,≥1/2 region)"]
end
E --> CSet["回收集 CSet<br/>按垃圾比例 + 停顿目标挑选"]
O --> CSet
CSet --> P["复制存活对象到空 region<br/>原 region 整体释放 → 无碎片"]
style CSet fill:#ffe0b2
style P fill:#c8e6c9
JVM 配置参数#
启用#
java -jar app.jar # JDK 9+ 默认就是 G1,无需指定
java -XX:+UseG1GC -Xmx8g -jar app.jar # 显式指定
关键调优参数#
| 参数 | 含义 | 默认 | 备注 |
|---|---|---|---|
-XX:MaxGCPauseMillis=<ms> |
期望最大停顿目标 | 200 | G1 据此挑 CSet;设太小会频繁 GC、吞吐降 |
-XX:G1HeapRegionSize=<size> |
单个 region 大小 | 按 -Xmx 自动选(1/2/4…32MB) |
影响 Humongous 判定与大对象处理 |
-XX:InitiatingHeapOccupancyPercent=<n>(IHOP) |
堆占用达 n% 启动并发标记 |
45 | JDK 9 起默认自适应(按历史调整) |
-XX:G1NewSizePercent=<n> |
年轻代最小占比 | 5 | 想给年轻代保底用 |
-XX:G1MaxNewSizePercent=<n> |
年轻代最大占比 | 60 | 限制年轻代上限 |
-XX:G1ReservePercent=<n> |
保留空闲堆占比(防疏散失败) | 10 | 频繁疏散失败(evacuation failure)时可调大 |
-XX:ParallelGCThreads=<n> |
STW 阶段并行线程数 | ≈ CPU 核数 | 容器内注意限额 |
-XX:ConcGCThreads=<n> |
并发标记线程数 | ≈ ParallelGCThreads/4 |
占用应用 CPU,过多影响吞吐 |
-XX:+G1EnableStringDeduplication |
字符串去重(相同内容共享) | 关 | 字符串密集场景省内存 |
# 典型:通用服务,8GB 堆,停顿目标 100ms
java -XX:+UseG1GC -Xmx8g \
-XX:MaxGCPauseMillis=100 \
-XX:InitiatingHeapOccupancyPercent=45 \
-XX:G1ReservePercent=15 \
-jar app.jar
各版本 G1 增强#
| 版本 | JEP | 增强 |
|---|---|---|
| JDK 9 | 248 | G1 成为默认;IHOP 自适应 |
| JDK 10 | 307 | 并行 Full GC(此前单线程兜底),JDK 11 LTS 承接 |
| JDK 12 | 344 | 可中止的混合回收(Abortable Mixed Collections),必要时提前中止以满足停顿目标 |
| JDK 14 | 345 | NUMA 感知,多 socket 机器上局部性更好 |
| JDK 12 | 346 | 检测空闲并及时归还未用堆内存给操作系统 |
适用场景#
- 通用首选:从 JDK 9 起,绝大多数服务端应用用默认 G1 就对。
- 较大堆(> ~4–6GB):G1 的 region 化 + 停顿预测在大堆上优势明显。
- 停顿要可控但不要求亚毫秒:G1 能把停顿压在几十到一两百毫秒量级(视堆和负载)。若要亚毫秒,上 ZGC。
与前代 GC 对比:CMS / Parallel → G1#
G1 接过 CMS「可控停顿」的衣钵,又补上它最大的短板:分区复制 → 无碎片。相对被它取代的 CMS(标记-清除、碎片化、退化长 Full GC),G1 用 region + 停顿预测把停顿做得可预测且不退化;相对 JDK 8 默认的 Parallel(全 STW、Full GC 整堆长停顿),G1 的停顿可控得多。
java -XX:+UseConcMarkSweepGC -Xmx8g -jar app.jar # 停顿较低,但碎片化→退化长 Full GC
java -XX:+UseParallelGC -Xmx8g -jar app.jar # 吞吐高,但 Full GC 整堆长停顿
java -jar app.jar # 默认 G1,停顿可控、无碎片
java -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -jar app.jar
常见坑 / 最佳实践#
- 别设太激进的
MaxGCPauseMillis:设到几毫秒 G1 也做不到,只会让 CSet 缩得很小、GC 更频繁,吞吐反降。从默认 200 开始,按观测调整。 - 别给 G1 设固定年轻代大小:
-Xmn、-XX:NewRatio、-XX:NewSize等对 G1 基本无效或会警告——G1 要靠动态调整年轻代 region 数来满足停顿目标,手动固定会破坏这套机制。 - 频繁
evacuation failure/to-space exhausted:说明 Mixed GC 时没有空 region 放存活对象。调大-XX:G1ReservePercent(如 15–20),或增大-Xmx。 - 偶尔的 Full GC 是兜底信号:G1 正常应靠 Young/Mixed GC 维持。频繁 Full GC 说明分配速率超过回收能力,需调 IHOP、增大堆,或排查对象分配热点。
ConcGCThreads别设太大:并发标记线程和应用争 CPU,过多会拖累吞吐。- Humongous 对象:超过 region 一半的大对象走 Humongous region,频繁分配大对象(如大数组)会拖慢 G1——必要时调大
G1HeapRegionSize。
小结#
G1 是 JDK 9 以来「停顿可控、无碎片、大堆友好」的默认选择。它用 region 化 + 停顿预测模型把单次回收约束在目标内,用复制式回收根除碎片化,并在 JDK 11/12 持续增强(并行 Full GC、可中止混合回收、NUMA 感知)。对绝大多数应用,信任默认的 G1、配合观测按需微调即可;只有对延迟有极致要求时才需考虑 ZGC。