JVMNotes

第 17 章:G1

zjc 于 2026-01-17 发布

这是《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。

风险:

  1. 大数组;
  2. 大字符串;
  3. 大查询结果;
  4. 大报文;
  5. 批量集合。

处理:

  1. 分页;
  2. 流式处理;
  3. 拆批;
  4. 限制响应大小;
  5. 观察 humongous 指标。

17.6 转移失败

常见概念:

Evacuation Failure / to-space exhausted

含义:回收时没有足够空间接收转移对象。

常见原因:

  1. 堆太小;
  2. 存活对象过多;
  3. 大对象过多;
  4. 并发标记太晚;
  5. 碎片或 Region 分配失败;
  6. 突发流量。

处理:

  1. 扩堆;
  2. 降低存活对象;
  3. 减少大对象;
  4. 提前并发标记;
  5. 调整停顿目标;
  6. 观察晋升。

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 适用场景

适合:

  1. 大堆;
  2. 多核;
  3. 交互式服务;
  4. 需要平衡吞吐和延迟;
  5. 希望默认方案稳妥。

不一定适合:

  1. 极低停顿需求,可评估 ZGC;
  2. 纯吞吐批处理,可评估 Parallel;
  3. 堆很小且资源紧张;
  4. 分配模式极端大对象;
  5. 无法承受额外内存结构开销。

17.10 生产案例

案例一:Mixed GC 后 Old 仍增长

观察:

jstat -gcutil <pid> 1000

GC 日志显示并发标记完成,但每次 Mixed 回收 Region 数量少。

方向:

  1. MaxGCPauseMillis 过小;
  2. 停顿目标限制回收量;
  3. 大对象占用;
  4. 存活对象高;
  5. Old 水位已接近危险。

处理:调整目标、减少大对象、扩堆、观察。

案例二:导出接口导致 Full GC

观察:

Humongous allocation
Evacuation Failure

处理:

  1. SQL 分页;
  2. 流式写出 CSV / Excel;
  3. 限制导出行数;
  4. 异步任务;
  5. 独立实例承接。

本章小结

G1 用 Region 化布局、并发标记和停顿预测模型实现吞吐与延迟的平衡。它适合大堆多核交互服务,但需要关注分配速率、晋升、大对象和转移失败。调优应先修代码和对象分配,再小步调整参数并用日志验证。

思考题

  1. G1 的 Region 有什么作用?
  2. Mixed GC 与 Young GC 的差异是什么?
  3. 什么是 Humongous Region?
  4. 转移失败的常见原因有哪些?
  5. MaxGCPauseMillis 设得越小越好吗?