Skip to content

G1:Region 化收集器,JDK 9+ 的默认选择

当 CMS 在内存碎片和 Full GC 的泥潭中挣扎时,G1 横空出世。

它用一个革命性的设计——把堆划分为 Region——解决了 CMS 的所有痛点,并成为 JDK 9+ 的默认垃圾收集器。


G1 的核心思想:Region 化

传统分代 vs G1 Region

┌─────────────────────────────────────────────────────────────┐
│  传统分代收集器                                             │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                         堆                            │   │
│  │  ┌───────────────┬───────────────────────────┐     │   │
│  │  │    年轻代      │         老年代             │     │   │
│  │  │ ┌───┬───┬───┐ │                           │     │   │
│  │  │ │Eden│S0 │S1 │ │                           │     │   │
│  │  │ └───┴───┴───┘ │                           │     │   │
│  │  └───────────────┴───────────────────────────┘     │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                              │
│  特点: Eden、S0、S1、老年代,大小固定,不可调整            │
│                                                              │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│  G1 收集器                                                   │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                         堆                            │   │
│  │  ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┐     │   │
│  │  │ Eden│ Eden│  S  │ Old │ Old │ Old │ Hum │     │   │
│  │  │     │     │     │     │     │     │     │     │   │
│  │  ├─────┼─────┼─────┼─────┼─────┼─────┼─────┤     │   │
│  │  │ Eden│  S  │ Old │ Old │ Hum │ Hum │ Eden│     │   │
│  │  │     │     │     │     │     │     │     │     │   │
│  │  ├─────┼─────┼─────┼─────┼─────┼─────┼─────┤     │   │
│  │  │ Old │ Old │ Eden│  S  │ Old │ Eden│  S  │     │   │
│  │  └─────┴─────┴─────┴─────┴─────┴─────┴─────┘     │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                              │
│  特点:堆被划分为多个 Region,每个 Region 可以是 Eden/Survivor/Old  │
│                                                              │
└─────────────────────────────────────────────────────────────┘

Region 的设计

java
// G1 的 Region 大小配置
public class G1RegionSize {
    // 默认:-XX:G1HeapRegionSize=1~32MB(自动计算)
    // 不能太小:太小会导致 RSet 膨胀
    // 不能太大:太大影响回收精度

    // Region 数量 = 堆大小 / Region 大小
    // 4GB 堆 / 2MB Region = 2048 个 Region
}

特殊 Region

类型说明特点
Humongous大对象(超过 50% Region 大小)连续多个 Region,优先回收
Old Region老年代对象组成老年代
Survivor Region存活对象S0/S1 角色
Eden Region新生代对象组成年轻代

G1 的回收过程

Young GC:年轻代收集

┌─────────────────────────────────────────────────────────────┐
│  Young GC 过程                                              │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  1. 选定所有年轻代 Region,Stop The World                   │
│     ┌─────────────────────────────────────────────────┐    │
│     │ [E][E][E][E][E][S][S] │ [Old][Old][Old][Hum]   │    │
│     │     年轻代 Regions      │      其他 Regions      │    │
│     └─────────────────────────────────────────────────┘    │
│                                                              │
│  2. 复制算法:将存活对象复制到 Survivor 或晋升到 Old        │
│     ┌─────────────────────────────────────────────────┐    │
│     │ [E][E][E][E] │ [S] │ [Old][Old][Old][Hum]    │    │
│     │  新 Eden       │存活 │      其他 Regions        │    │
│     └─────────────────────────────────────────────────┘    │
│                                                              │
│  3. 清理空 Region                                           │
│                                                              │
└─────────────────────────────────────────────────────────────┘

Mixed GC:混合收集

┌─────────────────────────────────────────────────────────────┐
│  Mixed GC 过程                                              │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  触发条件:老年代使用率超过阈值                              │
│  -XX:InitiatingHeapOccupancyPercent=45(默认)              │
│                                                              │
│  1. 初始标记(STW):标记 GC Roots                          │
│     - 年轻代收集时同时进行                                   │
│                                                              │
│  2. 并发标记:遍历对象图,不 STW                            │
│                                                              │
│  3. 最终标记(STW):修正并发变化                           │
│                                                              │
│  4. 筛选回收(STW):                                        │
│     - 根据停顿时间目标,选择回收价值最高的 Region            │
│     - 回收顺序:垃圾最多的 Region → 垃圾次多的 → ...        │
│                                                              │
│  5. 复制整理:将存活对象复制到空 Region                      │
│     - 同时完成 compaction(无碎片!)                        │
│                                                              │
└─────────────────────────────────────────────────────────────┘

G1 的核心优势

优势 1:可预测的停顿时间

bash
# 设置停顿时间目标
java -XX:+UseG1GC \
     -XX:MaxGCPauseMillis=200 \   # 希望停顿不超过 200ms
     your.Application

# G1 会动态调整:
# - 每次收集的 Region 数量
# - 优先收集垃圾最多的 Region
# - 牺牲部分吞吐量,换取可控停顿

优势 2:无内存碎片

CMS vs G1 整理效果对比

CMS(标记-清除):
┌─────────────────────────────────────────────────────────────┐
│  [██][  ][██][    ][██][  ][██][    ][██][██]             │
│  碎片,无法分配大对象                                        │
└─────────────────────────────────────────────────────────────┘

G1(复制整理):
┌─────────────────────────────────────────────────────────────┐
│  [██][██][██][██][██]        [空闲][空闲][空闲]            │
│  连续空间,可分配大对象                                      │
└─────────────────────────────────────────────────────────────┘

优势 3:内存布局灵活

java
// G1 可以动态调整各代大小
// CMS:Eden : Survivor : Old 比例固定
// G1:Region 数量和角色动态变化

Remembered Set(RSet)

为什么需要 RSet?

年轻代对象可能被老年代 Region 引用,扫描整个老年代太慢。

┌─────────────────────────────────────────────────────────────┐
│  跨 Region 引用问题                                         │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│     Region A (Old) ──引用──→ Region B (Eden)                │
│                                                              │
│  问题:回收 Eden 时,如何发现 Region B 中的对象?            │
│  方案:Region A 记录对 Region B 的引用                      │
│  工具:Remembered Set(RSet)                               │
│                                                              │
└─────────────────────────────────────────────────────────────┘

RSet 实现原理

java
// RSet 结构
public class RSet {
    // 每个 Region 都有一个 RSet
    // RSet 记录:谁引用了我
    // 其他 Region 对本 Region 对象的引用

    // Card Table 的进化
    // 1. Card Table:将老年代划分为多个 Card(通常 512 字节)
    // 2. RSet:每个 Card 对应一个 bit set,记录引用关系
}

RSet 的开销

堆大小Region 大小Region 数量RSet 开销
4GB2MB2048~1%
32GB4MB8192~2%
64GB8MB8192~2%

RSet 是 G1 实现可预测停顿的代价。


核心参数配置

bash
# 启用 G1
java -XX:+UseG1GC your.Application

# 停顿时间目标(最重要)
-XX:MaxGCPauseMillis=200

# Region 大小
-XX:G1HeapRegionSize=2m

# 触发 Mixed GC 的老年代阈值
-XX:InitiatingHeapOccupancyPercent=45

# Mixed GC 回收的老年代 Region 比例
-XX:G1HeapWastePercent=5

# 每次 Mixed GC 最多回收的 Region 数
-XX:G1MixedGCCountTarget=8

# JDK 11+ 推荐配置
java -XX:+UseG1GC \
     -XX:MaxGCPauseMillis=200 \
     -XX:G1HeapRegionSize=4m \
     -XX:InitiatingHeapOccupancyPercent=45 \
     -Xmx4g -Xms4g \
     your.Application

G1 调优经验

问题 1:频繁 Young GC

bash
# 症状:YGC 次数过多
# 原因:Eden Region 太小或对象分配太快

# 解决:增大年轻代
-XX:G1NewSizePercent=10    # 年轻代最小比例
-XX:G1MaxNewSizePercent=60 # 年轻代最大比例

问题 2:Mixed GC 时间过长

bash
# 症状:Mixed GC 停顿时间超过 MaxGCPauseMillis
# 原因:每次回收的 Region 太多

# 解决:减少每次 Mixed GC 的 Region 数
-XX:G1MixedGCCountTarget=16   # 增加回收次数
-XX:G1HeapWastePercent=10     # 允许更多垃圾

问题 3:Humongous 对象过多

bash
# 症状:大对象过多,导致频繁 Full GC
# 原因:对象超过 Region 大小的 50%

# 解决:增大 Region 大小,或优化代码减少大对象
-XX:G1HeapRegionSize=4m

G1 日志解读

text
# Young GC 日志
2024-01-15T10:30:00.123: [GC pause (G1 Evacuation Pause) (young)
    # 年轻代回收
    # Evacuation Pause:复制阶段
    Using 8 workers
    # 8 个 GC 线程
    2024-01-15T10:30:00.123: [GC pause (G1 Evacuation Pause) (young)
    # 开始
    (Eden: 1320.0M(1320.0M)->0.0B(1180.0M)
    # Eden: 使用 1.3GB → 回收后 0,使用量 0 → 回收后 Eden 变成 1.18GB
    Survivors: 15.0M->35.0M
    # Survivor 区:15MB → 35MB
    Heap: 2548.0M(4096.0M)->1323.0M(4096.0M)]
    # 堆:使用 2.5GB → 1.3GB
    [Times: user=0.80 sys=0.10, real=0.15 secs]
    # 停顿 150ms

# Mixed GC 日志
2024-01-15T10:35:00.000: [GC pause (G1 Evacuation Pause) (mixed)
    # 混合回收
    (Eden: 1100.0M(1180.0M)->0.0B(1200.0M)
    Survivors: 35.0M->40.0M
    Heap: 3200.0M(4096.0M)->1800.0M(4096.0M)
    # 包含老年代 Region 的回收
    [Times: user=1.20 sys=0.20, real=0.30 secs]

G1 vs CMS

对比项G1CMS
设计理念可预测停顿低停顿
分代Region 化分代传统分代
内存碎片无(复制整理)有(标记-清除)
Full GC有(但少)有(Serial Old)
并发阶段全程并发部分并发
停顿时间可配置不可控
适用版本JDK 9+JDK 8 及之前
默认收集器JDK 9+ 是

面试追问方向

  • G1 的 Region 大小是如何计算的?为什么不能手动设置得太小或太大?
  • G1 的 RSet 是什么?为什么需要它?有什么开销?
  • G1 的停顿时间目标是如何实现的?「回收价值最高」怎么衡量?
  • Humongous Region 是什么?为什么它可能导致 Full GC?
  • G1 的 Mixed GC 什么时候触发?和 CMS 的老年代收集有什么区别?
  • JDK 11+ 的 ZGC 和 G1 相比,各自的适用场景是什么?

基于 VitePress 构建