G1收集器原理#
一句话答案#
G1 将堆划分为等大 Region,优先回收垃圾最多的 Region(Garbage First),兼顾吞吐和停顿,JDK9+ 默认收集器。
核心要点
关键概念:
Remembered Set(RSet):
- 每个 Region 都有一个 RSet,记录哪些其他 Region 引用了本 Region 的对象
- 用途:Minor GC 时,不需要扫描整个老年代,只需扫描 RSet 中记录的引用
- 代价:RSet 的维护有额外内存开销(约 10%~20%)
Card Table:
- 老年代被划分为 512 字节的 Card,Card Table 记录哪些 Card 是”脏的”(被修改过)
- 用于精确定位哪些老年代位置引用了年轻代对象
Humongous Region:
- 大对象(>= Region 大小一半)直接分配到 Humongous Region
- 跨多个 Region 连续分配
- GC 时在 Old GC 阶段处理
G1 的停顿预测模型:
- G1 记录每个 Region 的回收时间和可回收垃圾量
- 根据
-XX:MaxGCPauseMillis目标,选择在目标时间内能回收最多垃圾的 Region 集合 - “Garbage First” 名字来自于优先回收垃圾占比最高的 Region
并发漏标:G1 为什么用 SATB(而非增量更新)?
- G1 解决并发漏标走的是 SATB(Snapshot-At-The-Beginning)路线:写前屏障记录”被删除/覆盖的旧引用”,破坏漏标条件②;按标记开始时的对象图快照标记,旧引用指向的白对象仍被标活。
- 代价:会把”标记期间本已死亡”的对象也保活 → 产生浮动垃圾,留到下次 GC;好处是 remark 只需处理 SATB 缓冲区(量小),停顿短——这与 G1 追求可预测低停顿的目标一致。
- 对比 CMS:CMS 用增量更新破坏条件①(记黑→白新增引用,remark 重扫黑对象,停顿偏长,无此类浮动垃圾)。两个漏标条件的完整推导见 垃圾回收算法 的「三色标记与并发漏标」主线。
G1 的完整 GC 流程:
1. Young GC(STW):
触发条件:Eden Region 被占满
流程:
Eden Region + Survivor Region → 标记存活对象(借助 RSet 避免全堆扫描)
→ 存活对象复制到新的 Survivor Region
→ 年龄达到阈值的对象晋升到 Old Region
→ 回收原 Eden 和 Survivor Region
特点:
全程 STW,但只涉及年轻代 Region,停顿较短(通常几毫秒到几十毫秒)
G1 会动态调整 Eden/Survivor 的 Region 数量以满足停顿目标plaintext2. 并发标记周期(Concurrent Marking Cycle):
触发条件:老年代占用达到 -XX:InitiatingHeapOccupancyPercent(默认 45%)
初始标记(Initial Mark) ← STW(借 Young GC 顺带完成,极短)
标记 GC Root 直接可达的对象
并发标记(Concurrent Mark) ← 与用户线程并发
从 GC Root 遍历整个堆,标记所有存活对象
使用 SATB(Snapshot-At-The-Beginning)算法保证并发一致性:
并发开始时对对象图做逻辑快照,期间引用变更通过写屏障记录到 SATB 缓冲区
最终标记(Final Mark / Remark) ← STW
处理 SATB 缓冲区中并发期间被删除的旧引用,完成标记(缓冲区量小,停顿短)
清理(Cleanup) ← 部分 STW + 部分并发
统计每个 Region 的存活对象比例(STW)
回收完全空闲的 Region(STW)
将需要回收的 Region 按垃圾比例排序,加入 CSet(并发)plaintext3. Mixed GC(混合回收):
触发条件:并发标记周期完成后自动触发
回收范围:所有 Young Region + 部分垃圾比例高的 Old Region
选择策略:优先选择垃圾最多的 Old Region(Garbage First 得名由来)
执行方式:分多轮执行,每轮都是 STW 的复制回收
控制参数:
-XX:G1MixedGCCountTarget=8 # 分 8 轮 Mixed GC 逐步完成(默认)
-XX:G1HeapWastePercent=5 # 可回收垃圾低于堆的 5% 时停止 Mixed GC
-XX:G1OldCSetRegionThresholdPercent=10 # 每轮最多回收 10% 的 Old Regionplaintext4. Full GC(退化回退):
触发条件:对象分配速度 > 回收速度(Allocation Failure / Evacuation Failure)
表现:JDK 9 及之前退化为单线程 Serial Old GC(停顿极长)
JDK 10+ 支持并行 Full GC(G1 自身实现),但仍应尽量避免
调优目标:通过调整 MaxGCPauseMillis 和堆大小,确保 Mixed GC 能跟上分配速度plaintextG1 GC 模式对比:
| 维度 | Young GC | Mixed GC | Full GC |
|---|---|---|---|
| 触发条件 | Eden 满 | 并发标记后 | 分配失败 |
| 回收范围 | Young Region | Young + 部分 Old | 整堆 |
| 是否 STW | 是 | 是 | 是(耗时长) |
| 频率 | 高 | 中 | 应避免 |
面试回答(2分钟版)
G1 是 JDK9 开始的默认垃圾收集器,最大的创新是把堆划分为约 2048 个大小相等的 Region(1-32MB),每个 Region 可以动态充当 Eden、Survivor、Old 或 Humongous 角色,不再是物理上连续的分代。G1 的核心思想是”Garbage First”,它维护一个停顿预测模型,记录每个 Region 的回收耗时和可回收垃圾量,根据 -XX:MaxGCPauseMillis 目标(默认 200ms)选择在目标时间内能回收最多垃圾的 Region 集合优先回收。跨 Region 引用通过 Remembered Set 解决,每个 Region 的 RSet 记录哪些其他 Region 引用了自己的对象,Minor GC 时不需要扫描整个老年代,只需扫描 RSet,代价是额外 10%-20% 的内存开销。大对象(大于等于 Region 一半)直接分配到 Humongous Region。G1 有三种 GC 模式:Young GC 在 Eden 满时触发只回收年轻代;当老年代占用达到 45% 会启动并发标记周期,分四步——初始标记借 Young GC 顺带完成、并发标记使用 SATB 算法遍历全堆、最终标记处理 SATB 缓冲区、清理统计各 Region 垃圾比例;标记完成后触发 Mixed GC,选择部分垃圾最多的老年代 Region 和全部年轻代一起回收,分多轮完成。如果回收速度跟不上分配速度,G1 会退化为 Full GC,这是需要调优避免的。
追问与易错
追问方向:
- “G1 的 Region 大小怎么确定?”→ 1-32MB,默认堆/2048 个 Region
- “Mixed GC 和 Full GC 的区别?”→ Mixed 选择部分 Region,Full 回退 Serial Old
- “G1 怎么处理跨 Region 引用?”→ Remembered Set,卡表记录跨区引用
易错点:
- ❌ “G1 不会 Full GC”——如果回收速度跟不上分配速度,会退化为 Full GC
- ❌ 混淆 Young GC 和 Mixed GC——Mixed GC 包含部分老年代 Region