G1垃圾回收器工作原理
约 1924 字大约 6 分钟
javag1gc
2025-03-11
概述
G1(Garbage-First)是JDK 9 以来的默认垃圾回收器,面向服务端应用设计,目标是在可控的停顿时间内获得尽可能高的吞吐量。G1打破了传统的固定分代布局,采用 Region化 的堆内存管理,兼具新生代和老年代的回收能力。
Region化堆结构
G1 将整个堆划分为若干大小相等的 Region(默认2048个,每个1~32MB),每个Region在逻辑上属于某个分代角色,但角色可以动态切换。
| Region类型 | 说明 |
|---|---|
| Eden | 新对象分配区 |
| Survivor | 存活对象暂存区 |
| Old | 老年代对象 |
| Humongous | 大对象(超过Region大小50%的对象) |
| Free | 空闲Region |
Humongous对象
当对象大小超过Region容量的50%时,被视为 Humongous 对象,直接分配到连续的 Humongous Region中。
Humongous 对象在 Young GC 或 Mixed GC 中都可能被回收(JDK 8u40+ 优化)。
G1回收过程总览
Young GC
Young GC 回收 所有 Eden Region 和 Survivor Region,将存活对象复制到新的Survivor Region 或晋升到 Old Region。
并发标记(Concurrent Marking)
当堆使用率达到 IHOP(Initiating Heap Occupancy Percent,默认45%)时触发并发标记。
阶段详解
1. 初始标记(Initial Mark)— STW
标记GC Roots直接可达的对象。这个阶段很短,通常搭载在Young GC上执行。
2. 并发标记(Concurrent Mark)— 并发
从初始标记的对象出发,遍历整个对象图。使用 SATB(Snapshot At The Beginning) 算法保证标记正确性。
SATB保证了:即使并发标记期间引用关系发生变化,标记开始时刻存活的对象不会被漏标。可能产生少量"浮动垃圾"(标记时存活但标记后死亡的对象),留到下次GC处理。
3. 最终标记(Remark)— STW
处理 SATB 写屏障记录的引用变更,完成标记。
4. 清理(Cleanup)— STW
- 统计每个Region中存活对象的比例
- 按回收价值排序(垃圾最多的Region优先)
- 回收完全为空的Region
- 重置RSet
Mixed GC
并发标记完成后,G1选择回收价值最高的若干Old Region,与所有Young Region一起进行混合回收。
Collection Set(CSet):G1每次GC回收的Region集合。Young GC的CSet = 所有Young Region;Mixed GC的CSet = 所有Young Region + 部分Old Region。
G1根据 -XX:MaxGCPauseMillis(默认200ms)控制每次Mixed GC选中的Old Region数量,保证在停顿时间目标内完成。
记忆集(Remembered Set)
每个Region维护自己的RSet,记录"哪些其他Region中有引用指向本Region"。
RSet使得GC不需要扫描整个堆来找跨Region引用,但维护RSet本身有额外的内存和CPU开销(约10%~20%的堆空间用于RSet)。
调优参数
| 参数 | 默认值 | 说明 |
|---|---|---|
-XX:+UseG1GC | JDK 9+默认 | 启用G1 |
-XX:MaxGCPauseMillis | 200ms | 目标停顿时间 |
-XX:G1HeapRegionSize | 自动(1~32MB) | Region大小 |
-XX:InitiatingHeapOccupancyPercent | 45 | 触发并发标记的堆占用比 |
-XX:G1MixedGCLiveThresholdPercent | 85 | Old Region存活率高于此值不回收 |
-XX:G1MixedGCCountTarget | 8 | 并发标记后Mixed GC最大次数 |
-XX:G1NewSizePercent | 5 | 新生代最小比例 |
-XX:G1MaxNewSizePercent | 60 | 新生代最大比例 |
-XX:G1ReservePercent | 10 | 预留空间防止晋升失败 |
-XX:ConcGCThreads | — | 并发标记线程数 |
-XX:ParallelGCThreads | — | STW阶段并行线程数 |
调优建议
1. 设置合理的停顿时间目标
# 不要设置过低,否则G1频繁GC但每次回收很少
-XX:MaxGCPauseMillis=200 # 默认值,大多数场景适用2. 避免Full GC
Full GC是G1的最后手段(单线程Serial Old),应尽量避免:
- 增大堆空间
- 降低IHOP让并发标记更早开始
- 检查是否有内存泄漏
3. 监控关键指标
# GC日志(JDK 9+统一日志)
-Xlog:gc*:file=gc.log:time,uptime,level,tags
# 关注:
# - Young GC频率和耗时
# - Mixed GC频率
# - To-space exhausted(晋升失败)
# - Full GC出现次数G1与其他收集器对比
| 特性 | CMS | G1 | ZGC |
|---|---|---|---|
| 算法 | 标记-清除 | 标记-整理(Region) | 标记-整理(Region) |
| 内存碎片 | 有 | 无 | 无 |
| 停顿时间 | 不可预测 | 可控(目标制) | 亚毫秒 |
| 堆大小 | <32GB | 4GB~几百GB | 几百GB~TB级 |
| 并发标记 | 增量更新 | SATB | 着色指针 |
| 适用场景 | 已弃用(JDK14) | 通用(默认GC) | 超低延迟 |
常见FAQ
Q: G1的Full GC什么时候触发?
A: 当并发标记未能及时完成、Mixed GC回收速度跟不上分配速度、或Humongous分配失败时,G1会退化为Full GC。JDK 10之后Full GC也支持并行化。
Q: 为什么G1不适合小堆?
A: G1的Region管理、RSet维护都有固定开销。堆太小(<4GB)时,这些开销占比过高,可能不如ParallelGC。G1在中大堆(4GB~几百GB)表现最佳。
Q: IHOP应该如何设置?
A: JDK 9+ 默认开启自适应IHOP(-XX:+G1UseAdaptiveIHOP),G1会根据历史数据自动调整。通常不需要手动设置。
总结
G1通过Region化堆内存、基于回收价值的Region选择策略、以及可控的停顿时间目标,成为Java 9以来的默认GC。它的核心优势在于"Garbage-First" — 优先回收垃圾最多的Region,在有限的停顿时间内获得最大的回收收益。理解G1的工作机制,有助于在生产环境中进行有效的GC调优。
贡献者
更新日志
9f6c2-feat: organize wiki content and refresh site setup于