Skip to content

G1 回收过程/优化建议

G1 的完整回收过程

G1 的收集过程比传统 GC 更复杂,分为多个阶段:

G1 完整收集周期:
┌────────────────────────────────────────────────────┐
│                                                     │
│  ┌──────────────────────────────────────────────┐  │
│  │ 1. 年轻代收集(Young GC)                      │  │
│  │    - Eden Region → Survivor Region           │  │
│  │    - STW(短暂停顿)                         │  │
│  └──────────────────────────────────────────────┘  │
│                         │                          │
│          ┌─────────────┴─────────────┐            │
│          ▼                           ▼            │
│  ┌────────────────────┐    ┌────────────────────┐ │
│  │ 2a. 并发标记周期    │    │ 2b. 年轻代收集     │ │
│  │    (Initial Mark)  │    │    (继续执行)     │ │
│  │    - STW(短暂)   │    │    - STW(短暂)   │ │
│  └────────────────────┘    └────────────────────┘ │
│                         │                          │
│                         ▼                          │
│  ┌──────────────────────────────────────────────┐  │
│  │ 3. 混合收集(Mixed GC)                        │  │
│  │    - 年轻代 Region + 老年代 Region           │  │
│  │    - 若干次混合收集                         │  │
│  │    - STW(可预测停顿)                       │  │
│  └──────────────────────────────────────────────┘  │
│                         │                          │
│                         ▼                          │
│  ┌──────────────────────────────────────────────┐  │
│  │ 4. 重新标记/清理(Remark/Cleanup)           │  │
│  │    - 短暂 STW                               │  │
│  │    - 计算最终 CSet                          │  │
│  └──────────────────────────────────────────────┘  │
│                         │                          │
│                         ▼                          │
│                    返回 1. 年轻代收集              │
│                                                     │
└────────────────────────────────────────────────────┘

年轻代收集(Young GC)

详细过程

Young GC 执行过程:

1. 阶段:选择收集集合(CSet)
   └─ 选定所有 Eden Region 和 Survivor Region

2. 阶段:多线程并行复制
   └─ 把 Eden 和 Survivor 中的存活对象
      复制到新的 Survivor 和 Old Region

3. 阶段:更新 RSet
   └─ 更新被引用的 Region 的 RSet

4. 阶段:释放空 Region
   └─ 被清空的 Region 变成 Free Region

Young GC 的日志

2026-03-22T10:00:00.123: [GC pause (G1 Evacuation Pause) (young)
  Using 8 workers
  , 0.0123456 secs]
   [Eden: 1536.0M(1536.0M)->0.0B
    Survivors: 96.0M->128.0M
    Heap: 4096.0M(4096.0M)->2048.0M(4096.0M)]

并发标记周期

G1 的并发标记周期是它最重要的特性之一:

并发标记周期:

1. 初始标记(Initial Mark)── STW
   └─ 标记从 Survivor Region 晋升的对象

2. 根区域扫描(Root Region Scan)── 并发
   └─ 扫描 Survivor Region 中的对象引用

3. 并发标记(Concurrent Mark)── 并发
   └─ 从 GC Roots 出发,标记所有存活对象

4. 重新标记(Remark)── STW
   └─ 修正并发期间的引用变化(SATB 快照)

5. 清理(Cleanup)── 短暂 STW
   └─ 计算每个 Region 的回收价值

SATB:G1 的标记算法

G1 使用 SATB(Snapshot-At-The-Beginning) 算法解决并发标记期间的对象消失问题:

SATB 的原理:

GC 开始时,对所有跨 Region 的引用做快照

并发标记期间,对象被删除引用 → 记录到「待处理列表」

Remark 时,所有在快照中被标记但现在不可达的对象
被标记为存活

这些「快照中存活但实际上已死亡」的对象
被称为「浮动垃圾」

浮动垃圾

浮动垃圾(Floating Garbage):

并发标记期间产生的垃圾,下次 GC 时才能回收:

1. 并发标记时,对象 X 被标记为存活
2. 标记完成后,用户线程清除了对 X 的引用
3. X 现在是垃圾,但已标记完成
4. 必须等到下一次 GC 才能回收 X

混合收集(Mixed GC)

Mixed GC 的触发

当满足以下条件时,G1 触发混合收集:

触发条件:
  老年代使用率 > InitiatingHeapOccupancyPercent
  (默认 45%)

Mixed GC 的过程

Mixed GC 执行过程:

1. 选定 CSet:
   ├─ 所有年轻代 Region
   └─ 若干老年代 Region(按回收价值排序)

2. 多线程并行收集:
   ├─ 年轻代 Region → 复制到 Survivor/Old
   └─ 老年代 Region → 标记-清除(或复制到其他 Region)

3. 清理:释放空 Region

混合收集次数

G1 会执行多次混合收集,直到满足条件:

bash
# 一次混合收集周期最多多少次混合 GC
-XX:G1MixedGCCountTarget=8  # 默认

G1 优化建议

优化一:设置合理的停顿目标

bash
# 不要设置过小的停顿目标(否则 G1 无法完成任务)
java -XX:MaxGCPauseMillis=50 MyApp  # 太激进,GC 无法完成

# 合理的范围:100~500ms
java -XX:MaxGCPauseMillis=200 MyApp  # 推荐

优化二:增大堆或年轻代

如果年轻代收集太频繁:

bash
# 增大年轻代,减少 MinorGC 频率
java -Xmn2g -XX:+UseG1GC MyApp

# 或者增大总堆大小
java -Xms8g -Xmx8g -XX:+UseG1GC MyApp

优化三:调整混合收集次数

如果混合收集次数太多(影响吞吐量):

bash
# 减少混合收集次数
java -XX:G1MixedGCCountTarget=4 MyApp

# 提高混合收集阈值
java -XX:G1MixedGCLiveThresholdPercent=90 MyApp

优化四:减少 Humongous 对象

Humongous 对象(> Region 大小 50%)分配效率低:

bash
# 如果大量使用大数组,增大 Region 大小
java -XX:G1HeapRegionSize=8m MyApp

# 或者调整 Humongous 阈值
# (但这不是公开参数)

G1 的 GC 日志分析

bash
# 完整的 G1 GC 日志
java -Xms4g -Xmx4g \
     -XX:+UseG1GC \
     -XX:MaxGCPauseMillis=200 \
     -XX:+PrintGCDetails \
     -XX:+PrintGCDateStamps \
     -XX:+PrintTenuringDistribution \
     MyApp

日志解读

# 年轻代收集
2026-03-22T10:00:00.000: [GC pause (G1 Evacuation Pause) (young)
  [Eden: 2048.0M(2048.0M)->0.0B
   Survivors: 128.0M->256.0M
   Heap: 4096.0M(4096.0M)->2560.0M(4096.0M)]
  0.0456789 secs]

# 并发标记
2026-03-22T10:00:30.000: [GC pause (G1 Evacuation Pause) (young)
2026-03-22T10:00:30.100: [GC concurrent-root-region-scan-end, 0.0012345 secs]
2026-03-22T10:00:30.200: [GC concurrent-mark-end, 0.0987654 secs]

# 混合收集
2026-03-22T10:00:31.000: [GC pause (G1 Evacuation Pause) (mixed)
  [Eden: 0.0B(0.0B)->0.0B
   Survivors: 256.0M->128.0M
   Heap: 4096.0M(4096.0M)->2048.0M(4096.0M)]
  [Collection Sets: 512.0M selected]
  0.0789012 secs]

本节小结

G1 回收过程与优化:

阶段STW说明
Young GC年轻代 Region 复制
Initial Mark标记 Survivor 晋升的对象
Concurrent Mark并发标记存活对象
Remark修正并发期间的引用变化
Mixed GC年轻代 + 老年代 Region 收集
Cleanup是(短暂)计算回收价值,释放空 Region

G1 优化的核心原则:

  • 设置合理的 MaxGCPauseMillis(不要过小)
  • 增大年轻代减少 MinorGC 频率
  • 增大堆或调整 NewRatio 改善吞吐量
  • 监控 GC 日志,根据实际情况调整

理解 G1 的回收过程,是进行精细调优的基础。

到这里,G1 专题全部完成。接下来我们来看 新型回收器(Epsilon/Shenandoah/ZGC)

基于 VitePress 构建