这是《JVM 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 G1(Garbage First)是现代 HotSpot 的主流收集器之一。它把堆划分为多个 Region,优先回收垃圾比例高的区域,通过停顿预测模型尽量满足目标停顿,适合大堆交互服务。
17.1 核心思想
Heap
|-- Region 0 (Eden)
|-- Region 1 (Survivor)
|-- Region 2 (Old)
|-- Region 3 (Free)
+-- Region 4 (Humongous)
特点:
| 特点 | 说明 |
|---|---|
| Region 化 | 不要求物理连续分代 |
| 垃圾优先 | 优先回收收益高的区域 |
| 停顿预测 | 根据 pause target 建模型 |
| 整理为主 | 复制存活对象降低碎片 |
| 并发标记 | 识别老年代垃圾 |
17.2 启用与目标
java -XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-Xms4g -Xmx4g \
-jar app.jar
常见参数:
| 参数 | 说明 |
|---|---|
MaxGCPauseMillis |
软停顿目标 |
G1HeapRegionSize |
Region 大小 |
InitiatingHeapOccupancyPercent |
触发并发标记的全堆占比 |
G1ReservePercent |
预留空间 |
ConcGCThreads |
并发线程 |
ParallelGCThreads |
STW 并行线程 |
停顿目标是软目标。设置得越小,可能通过减少回收工作量实现,但 GC 更频繁、吞吐更低。
17.3 回收活动
Young GC:
Eden / Survivor Regions 满
-> STW
-> 拷贝存活对象到新 Survivor / Old
-> 回收空 Regions
Mixed GC:
并发标记完成后
-> 回收 Young Regions
-> 加上部分高收益 Old Regions
Full GC:
并发标记跟不上 / 晋升失败 / Metaspace 等资源不足
-> 单线程或多线程 Full GC,具体随 JDK 版本演进
目标是尽量避免 Full GC。
17.4 并发标记周期
Initial Mark
-> Root Region Scan
-> Concurrent Mark
-> Remark
-> Cleanup
-> Mixed GC
阶段:
| 阶段 | STW | 说明 |
|---|---|---|
| Initial Mark | 是 | 搭载一次 Young GC |
| Root Region Scan | 否 | 扫描存活 Region 引用 |
| Concurrent Mark | 否 | 遍历对象图 |
| Remark | 是 | 处理剩余引用 |
| Cleanup | 部分 | 统计和筛选 Region |
| Mixed GC | 是 | 分批回收 Region |
17.5 Region 与大对象
Region 大小通常为 1MB 到 32MB 的 2 的幂,由堆大小自动决定,也可显式设置:
-XX:G1HeapRegionSize=4m
大对象:
对象 >= 一半 Region
-> Humongous Region
连续大对象会占用多个连续 Region。
风险:
- 大数组;
- 大字符串;
- 大查询结果;
- 大报文;
- 批量集合。
处理:
- 分页;
- 流式处理;
- 拆批;
- 限制响应大小;
- 观察 humongous 指标。
17.6 转移失败
常见概念:
Evacuation Failure / to-space exhausted
含义:回收时没有足够空间接收转移对象。
常见原因:
- 堆太小;
- 存活对象过多;
- 大对象过多;
- 并发标记太晚;
- 碎片或 Region 分配失败;
- 突发流量。
处理:
- 扩堆;
- 降低存活对象;
- 减少大对象;
- 提前并发标记;
- 调整停顿目标;
- 观察晋升。
17.7 GC 日志
启用:
java -Xlog:gc*:file=gc.log:time,uptime,level,tags:filecount=10,filesize=50m \
-XX:+UseG1GC \
-jar app.jar
关键指标:
| 指标 | 含义 |
|---|---|
| Pause Young | Young GC 停顿 |
| Pause Mixed | Mixed GC 停顿 |
| Concurrent Mark Cycle | 并发标记 |
| Humongous allocation | 大对象分配 |
| Evacuation Failure | 转移失败 |
| Heap before / after | 堆变化 |
| Post-GWUnit | G1 内部单位,以日志说明为准 |
用 GCEasy、GCViewer、JDK Mission Control 或自建脚本分析趋势。
17.8 调优流程
1. 采集稳定流量下的 GC 日志
2. 明确 P99 / 吞吐 / CPU 目标
3. 先修复大对象和泄漏
4. 观察分配速率、晋升、并发标记时机
5. 小步调整一个参数
6. 压测对比
7. 固化配置
8. 继续监控
常见调参方向:
| 问题 | 方向 |
|---|---|
| Young 频繁 | 降低分配、调整目标或堆 |
| Mixed 不够快 | 检查并发标记和 Old 水位 |
| 转移失败 | 扩堆、减少存活和大对象 |
| 停顿长 | 降低目标、减少 Region 工作量 |
| CPU 高 | 并发线程过多或分配过高 |
17.9 G1 适用场景
适合:
- 大堆;
- 多核;
- 交互式服务;
- 需要平衡吞吐和延迟;
- 希望默认方案稳妥。
不一定适合:
- 极低停顿需求,可评估 ZGC;
- 纯吞吐批处理,可评估 Parallel;
- 堆很小且资源紧张;
- 分配模式极端大对象;
- 无法承受额外内存结构开销。
17.10 生产案例
案例一:Mixed GC 后 Old 仍增长
观察:
jstat -gcutil <pid> 1000
GC 日志显示并发标记完成,但每次 Mixed 回收 Region 数量少。
方向:
MaxGCPauseMillis过小;- 停顿目标限制回收量;
- 大对象占用;
- 存活对象高;
- Old 水位已接近危险。
处理:调整目标、减少大对象、扩堆、观察。
案例二:导出接口导致 Full GC
观察:
Humongous allocation
Evacuation Failure
处理:
- SQL 分页;
- 流式写出 CSV / Excel;
- 限制导出行数;
- 异步任务;
- 独立实例承接。
本章小结
G1 用 Region 化布局、并发标记和停顿预测模型实现吞吐与延迟的平衡。它适合大堆多核交互服务,但需要关注分配速率、晋升、大对象和转移失败。调优应先修代码和对象分配,再小步调整参数并用日志验证。
思考题
- G1 的 Region 有什么作用?
- Mixed GC 与 Young GC 的差异是什么?
- 什么是 Humongous Region?
- 转移失败的常见原因有哪些?
MaxGCPauseMillis设得越小越好吗?