Skip to content

G1 垃圾收集器

JDK 9 把 G1 变成了默认垃圾收集器

这意味着:从 JDK 9 开始,如果你在启动参数里没有指定 -XX:+UseG1GC,你用的就是 G1。

为什么是 G1

之前的默认收集器是 CMS,但 CMS 有两个致命问题:

  1. 内存碎片化:长时间运行后,内存碎片化严重
  2. 并发失败:如果并发标记跟不上垃圾产生的速度,就会退化成串行 Full GC,停顿时间可能达到几秒

G1 的设计目标:可预测的停顿时间 + 高吞吐量

G1 的核心思路

分区(Region)

G1 把堆内存分成多个大小相等的 Region(默认 1MB-32MB):

┌─────────────────────────────────────────────────────────────────┐
│                         G1 Heap                                  │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐             │
│  │ Eden    │ │ Eden    │ │ Survivor│ │ Eden    │             │
│  │  Region │ │  Region │ │  Region │ │  Region │             │
│  └─────────┘ └─────────┘ └─────────┘ └─────────┘             │
│                                                                 │
│  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐             │
│  │ Survivor│ │ Survivor│ │  Old    │ │  Old    │             │
│  │  Region │ │  Region │ │  Region │ │  Region │             │
│  └─────────┘ └─────────┘ └─────────┘ └─────────┘             │
│                                                                 │
│  ┌─────────────────────────┐ ┌─────────────┐                  │
│  │     Humongous Region     │ │  Free       │                  │
│  │     (> 50% Region)       │ │  Region     │                  │
│  └─────────────────────────┘ └─────────────┘                  │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Region 的大小是固定的(由 -XX:G1HeapRegionSize 设置),这解决了 CMS 的内存碎片问题。

停顿时间预测

G1 会跟踪每个 Region 的回收价值(回收多少垃圾 + 花多少时间),然后优先回收价值最高的 Region:

┌─────────────────────────────────────────────────────────────────┐
│                    停顿时间预测模型                                │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  G1 维护一个 Region 列表,按回收价值排序:                         │
│                                                                 │
│  Region E (价值: 80MB/5ms)  ←── 优先回收                        │
│  Region A (价值: 60MB/4ms)                                       │
│  Region B (价值: 40MB/3ms)                                       │
│  Region C (价值: 20MB/4ms)                                       │
│                                                                 │
│  目标停顿时间: 200ms                                             │
│  回收 E + A + B = 180MB / 12ms  ←── 在目标内                    │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

你可以通过 -XX:MaxGCPauseMillis=200 设置目标停顿时间(默认 200ms)。

年轻代收集

G1 的年轻代也是 Eden + Survivor 的结构,只是用 Region 来表示:

bash
# 年轻代初始大小
java -Xms4g -Xmx4g \
     -XX:NewSize=1g -XX:MaxNewSize=1g \
     -XX:+UseG1GC \
     -jar app.jar

# 设置目标停顿时间
java -XX:MaxGCPauseMillis=200 \
     -XX:+UseG1GC \
     -jar app.jar

年轻代收集(Young GC):

Young GC 前:
┌──────┬──────┬──────┬──────┬──────┐
│ Eden │ Eden │ S0   │ S1   │ Old  │
│ 60%  │ 40%  │ 80%  │  0%  │ 45%  │
└──────┴──────┴──────┴──────┴──────┘

Young GC 后:
┌──────┬──────┬──────┬──────┬──────┐
│ Eden │ Eden │ S0   │ S1   │ Old  │
│  0%  │ 10%  │ 20%  │  0%  │ 48%  │
└──────┴──────┴──────┴──────┴──────┘

混合收集(Mixed GC)

当老年代占比超过阈值(InitiatingHeapOccupancyPercent,默认 45%),G1 会触发并发标记周期

并发标记周期:
┌─────────────────────────────────────────────────────────────────┐
│                                                                 │
│  1. 初始标记 (STW)    - 标记 GC Roots 直接引用的对象            │
│       ↓                                                           │
│  2. 并发标记        - 遍历对象图,记录存活对象                   │
│       ↓                                                           │
│  3. 最终标记 (STW)   - 完成标记,处理漏掉的引用                   │
│       ↓                                                           │
│  4. 筛选回收 (STW)   - 按回收价值排序,回收 selected Set          │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

标记完成后,G1 会执行混合收集,回收年轻代 + 部分老年代 Region。

调优参数

核心参数

bash
# 启用 G1(JDK 9+ 默认启用)
java -XX:+UseG1GC -jar app.jar

# 目标停顿时间
java -XX:MaxGCPauseMillis=200 -XX:+UseG1GC -jar app.jar

# Heap Region 大小(1MB, 2MB, 4MB, 8MB, 16MB, 32MB)
java -XX:G1HeapRegionSize=16m -XX:+UseG1GC -jar app.jar

# 触发混合回收的老年代阈值
java -XX:InitiatingHeapOccupancyPercent=50 -XX:+UseG1GC -jar app.jar

# 混合回收时最多回收多少个老年代 Region
java -XX:G1MixedGCCountTarget=8 -XX:+UseG1GC -jar app.jar

完整配置示例

bash
java -Xms4g -Xmx4g \
     -XX:+UseG1GC \
     -XX:MaxGCPauseMillis=200 \
     -XX:G1HeapRegionSize=4m \
     -XX:InitiatingHeapOccupancyPercent=45 \
     -XX:G1MixedGCCountTarget=8 \
     -XX:G1HeapWastePercent=5 \
     -XX:+ParallelRefProcEnabled \
     -Xlog:gc*:file=gc.log \
     -jar app.jar

参数解释

参数默认值说明
MaxGCPauseMillis200ms目标停顿时间
G1HeapRegionSize自动Region 大小
InitiatingHeapOccupancyPercent45%触发并发标记的阈值
G1MixedGCCountTarget8混合回收次数
G1HeapWastePercent5%允许的浪费空间比例

监控与诊断

GC 日志

bash
# 详细 GC 日志
java -Xlog:gc*=debug:file=gc.log \
     -XX:+UseG1GC \
     -jar app.jar

# 实时查看
jstat -gcutil <pid> 1000

# 使用 G1CardTables
jinfo -flag G1HeapRegionSize <pid>

GC 日志分析

[GC pause (G1 Evacuation Pause) (young)]
GC concurrent humongous allocation.

关键指标:

  • Evacuation Pause:年轻代或混合收集的停顿
  • Humongous Allocation:分配大对象(超过 50% Region)
  • Conc Marking:并发标记周期

常用诊断命令

bash
# 查看当前 GC 配置
jinfo -flags <pid>

# 查看堆使用
jhsdb jmap --heap --pid <pid>

# 分析 GC 原因
jcmd <pid> GC.class_histogram
jcmd <pid> GC.heap_info

常见问题与解决

问题一:Full GC 频繁

原因:对象分配速率过高,混合回收来不及回收老年代
解决:
  1. 增大堆内存 -Xms4g -Xmx4g
  2. 调整目标停顿时间 -XX:MaxGCPauseMillis=300
  3. 降低触发阈值 -XX:InitiatingHeapOccupancyPercent=50

问题二:停顿时间过长

原因:Region 太大或太小,或混合回收选择的 Region 太多
解决:
  1. 减小 Region 大小 -XX:G1HeapRegionSize=2m
  2. 减小目标停顿时间让 G1 更保守
  3. 减少混合回收次数 -XX:G1MixedGCCountTarget=4

问题三:大对象分配失败

原因:Humongous Region 碎片化
解决:
  1. 增大 Region 大小 -XX:G1HeapRegionSize=16m
  2. 减少大对象创建

小结

G1 是 JDK 9+ 的默认收集器,适合大多数场景:

场景推荐配置
通用场景默认配置即可
低延迟需求-XX:MaxGCPauseMillis=100
大内存-Xms8g -Xmx8g -XX:G1HeapRegionSize=8m
高吞吐量考虑 Parallel GC

记住:不要过早优化。先跑默认配置,用 GC 日志观察表现,确实有问题再调参。

基于 VitePress 构建