G1垃圾收集器原理与调优
G1(Garbage First)是 JDK 9+ 的默认垃圾收集器,设计目标是替代 CMS,在可预测的停顿时间内获得最高吞吐量。
G1 的设计目标
- 可预测的停顿时间:设置目标停顿时间,G1 尽量遵守
- 高吞吐量:整体吞吐量不低于 CMS
- 无内存碎片:采用复制算法,整理内存
Region 化的堆结构
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐ │ E │ S │ O │ O │ H │ E │ S │ O │ E │ O │ └────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
E = Eden Region(年轻代) S = Survivor Region O = Old Region(老年代) H = Humongous Region(大对象,占连续多个Region)
Region大小:1MB, 2MB, 4MB, 8MB, 16MB, 32MB
|
大对象处理:
- 对象 > 0.5 Region:放入 Humongous Region
- 对象 > 1 Region:占用连续多个 Humongous Region
- Humongous Region 直接分配在老年代
核心数据结构
Remembered Set(记忆集)
每个Region维护一个RSet,记录哪些Region引用了本Region的对象
Region A (Old) ├── RSet: [Region B, Region C] │ └── Region B (Eden) 引用了 A 中的对象 │ └── Region C (Old) 引用了 A 中的对象
|
作用:避免全堆扫描,Minor GC 时只需扫描相关 Region。
Collection Set(CSet)
每次 GC 选择的一组 Region,是需要被回收的候选区域。
G1 的回收过程
Young GC
阶段1:选择所有 Eden + Survivor Region 进入 CSet 阶段2:复制存活对象到新的 Survivor / Old Region 阶段3:清空回收的 Region
Eden Region × 10 + Survivor Region × 2 ↓ 复制存活对象 ↓ Survivor Region × 1(或晋升到 Old)
|
特点:
- 完全 stop-the-world
- 多线程并行复制
- 目标是尽快完成
Concurrent Marking Cycle(并发标记周期)
当堆内存使用率达到阈值时触发:
1. Initial Mark(初始标记)
STW,标记 GC Roots 直接引用的对象 依附于 Young GC,几乎无额外开销
|
2. Root Region Scanning(根区域扫描)
扫描 Survivor Region 中引用老年代的对象 并发执行,必须在 Young GC 前完成
|
3. Concurrent Mark(并发标记)
从 GC Roots 开始遍历整个堆,标记存活对象 与用户线程并发执行 使用 SATB(Snapshot-At-The-Beginning)算法
|
SATB 算法:
- 标记开始时做一个快照
- 并发期间新分配的对象默认存活
- 通过 write barrier 记录引用变化
STW,处理 SATB 队列中的引用变化 比 CMS 的重新标记快(SATB 比 Incremental Update 高效)
|
5. Cleanup(清理)
STW,统计存活对象,识别完全空闲的 Region 并发清理空闲 Region
|
Mixed GC
并发标记完成后,G1 知道哪些 Region 垃圾最多,开始 Mixed GC:
回收策略: 1. 所有 Eden Region(必须回收) 2. 所有 Survivor Region(必须回收) 3. 部分垃圾最多的 Old Region(根据停顿目标选择数量)
目标:在 MaxGCPauseMillis 内回收尽可能多的垃圾
|
关键参数
基本参数
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:G1HeapRegionSize=16m
-XX:InitiatingHeapOccupancyPercent=45
|
进阶参数
-XX:G1ReservePercent=10
-XX:ConcGCThreads=4
-XX:ParallelGCThreads=8
-XX:G1RSetUpdatingPauseTimePercent=5
-XX:G1HeapWastePercent=5
-XX:G1MixedGCCountTarget=8 -XX:G1MixedGCLiveThresholdPercent=85
|
调优策略
1. 调整停顿时间目标
-XX:MaxGCPauseMillis=100
-XX:MaxGCPauseMillis=500
|
注意:停顿时间和吞吐量是反比关系,需要平衡。
2. 避免大对象
byte[] hugeArray = new byte[100 * 1024 * 1024];
|
3. 监控指标
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/path/to/gc.log
-Xlog:gc*:file=/path/to/gc.log:time,uptime,level,tags
|
4. 常见问题
Evacuation Failure(晋升失败)
日志:to-space exhausted 原因:复制存活对象时,Survivor/Old 空间不足 解决: - 增大堆内存 - 降低 MaxGCPauseMillis(回收更多Region) - 提前触发并发标记(降低 IHOP)
|
Humongous Allocation Failure
日志:G1 Humongous Allocation 原因:大对象分配失败 解决: - 避免大对象 - 增大Region大小 - 增大堆内存
|
G1 vs CMS
| 特性 |
G1 |
CMS |
| 算法 |
复制+整理 |
标记-清除 |
| 碎片 |
无 |
有 |
| 停顿时间 |
可预测 |
较短但不可预测 |
| 内存占用 |
较高(RSet) |
较低 |
| 大对象 |
Region化 |
直接入Old |
| 调优复杂度 |
简单 |
复杂 |
| 推荐版本 |
JDK 9+ |
JDK 8 及之前 |
生产环境配置示例
java \ -Xms16g -Xmx16g \ -XX:+UseG1GC \ -XX:MaxGCPauseMillis=200 \ -XX:InitiatingHeapOccupancyPercent=35 \ -XX:+PrintGCDetails \ -XX:+PrintGCDateStamps \ -Xloggc:/var/log/app/gc.log \ -jar application.jar
|
总结
G1 通过 Region 化和 Remembered Set,实现了:
- 可预测的停顿时间:控制每次回收的 Region 数量
- 无内存碎片:复制算法整理内存
- 高并发友好:多数阶段与用户线程并发
对于大多数现代 Java 应用,G1 是首选收集器。调优的核心是找到停顿时间和吞吐量的最佳平衡点。