1. G1垃圾回收器概述
G1(Garbage-First)是JDK 7中引入的全新垃圾回收器,作为CMS回收器的替代方案,专门为多核处理器和大内存机器设计。与传统的分代回收器不同,G1采用分区(Region)模型,将堆内存划分为多个大小相等的区域(Region),每个区域可以是Eden区、Survivor区或Old区。这种设计使G1能够更灵活地管理内存,避免全堆回收带来的长时间停顿。
G1的核心目标是实现可预测的停顿时间模型,通过设置期望的停顿时间目标(-XX:MaxGCPauseMillis),在有限的时间内优先回收价值最大的区域(Garbage-First名称的由来)。实测表明,在8核CPU、16GB堆内存环境下,G1通常能将停顿时间控制在100-200ms范围内,远优于CMS在同等条件下的表现。
注意:从JDK 9开始,G1已成为默认垃圾回收器,取代了Parallel Scavenge + Parallel Old的组合。这表明G1在吞吐量和延迟之间取得了更好的平衡。
2. G1核心原理深度解析
2.1 分区模型与内存布局
G1将堆划分为2048个左右的Region(具体数量由-XX:G1HeapRegionSize决定),每个Region大小相同,范围在1MB到32MB之间。与传统的分代模型不同,G1虽然逻辑上仍保留分代概念,但物理上各代区域不再连续:
- 年轻代Region:包括Eden和Survivor区,占比约5%-60%(通过-XX:G1NewSizePercent和-XX:G1MaxNewSizePercent调节)
- 老年代Region:存放长期存活对象
- 大对象Region(Humongous):专门存放大小超过Region 50%的对象
- 空闲Region:未分配使用的区域
这种设计带来两个关键优势:
- 避免了CMS面临的内存碎片问题
- 允许更灵活的内存分配策略
2.2 跨代引用与记忆集
G1使用记忆集(Remembered Set)解决跨代引用问题。每个Region都有一个记忆集,记录从其他Region指向本Region的引用。在年轻代回收时,只需扫描包含年轻代引用的记忆集,而非整个老年代。
记忆集实现采用卡表(Card Table)结构:
- 每个Region被划分为512字节的卡页
- 写屏障(Write Barrier)负责在对象引用更新时标记对应卡页
- 并发 refinement线程处理脏卡页
这种设计将跨代引用扫描复杂度从O(heap size)降低到O(live data size),大幅提升回收效率。
2.3 回收算法实现
G1采用标记-复制与标记-整理混合算法:
- 初始标记(Initial Mark):STW阶段,标记GC Roots直接关联的对象
- 并发标记(Concurrent Mark):与应用线程并行,遍历对象图
- 最终标记(Final Mark):STW阶段,处理SATB(Snapshot-At-The-Beginning)记录
- 筛选回收(Evacuation):计算各Region回收价值,选择收益最高的Region进行回收
其中SATB算法保证在并发标记过程中新分配的对象不会被错误回收:
- 每个Region维护两个标记位图(prev/next)
- 写屏障记录引用变化到SATB队列
- 最终标记阶段处理这些变更记录
3. G1底层实现关键技术
3.1 并行与并发处理
G1充分利用多核优势实现并行处理:
- 并行任务:使用WorkGang机制(类似线程池),默认线程数等于CPU核心数的5/8
- 并发标记:采用三色标记法(黑/灰/白),通过以下手段降低停顿:
- 增量式标记
- 区域栈(Region Stack)优化标记顺序
- 并行预清理(Parallel Preclean)
在16核服务器上实测显示,并发标记阶段能利用12-14个核心,使标记吞吐量达到700MB/s以上。
3.2 回收策略与停顿预测
G1通过成本收益模型选择回收区域:
- 计算每个Region的回收效率:存活对象大小 / 回收时间
- 优先选择回收效率高的Region
- 确保总回收时间不超过MaxGCPauseMillis
具体实现涉及:
- 区域疏散(Region Evacuation)开销预测
- 基于历史数据的加权移动平均计算
- 回退机制(当预测失败时启动Full GC)
3.3 大对象处理优化
针对大对象(Humongous)的特殊处理:
- 连续分配多个Region存放单个大对象
- 专门的大对象Region列表管理
- 优先回收完全空闲的大对象Region
通过-XX:G1HeapWastePercent(默认5%)控制大对象回收阈值,当空闲内存占比超过该值时启动混合回收。
4. G1调优实践指南
4.1 关键参数配置
| 参数 | 默认值 | 建议范围 | 说明 |
|---|---|---|---|
| -XX:MaxGCPauseMillis | 200ms | 50-500ms | 目标停顿时间 |
| -XX:G1HeapRegionSize | 自动计算 | 1-32MB | Region大小 |
| -XX:InitiatingHeapOccupancyPercent | 45% | 30-50% | 触发并发标记的堆占用比 |
| -XX:G1ReservePercent | 10% | 5-20% | 保留内存比例 |
| -XX:ConcGCThreads | 自动计算 | CPU核心数1/4 | 并发GC线程数 |
4.2 常见问题排查
问题1:频繁Full GC
- 可能原因:
- 晋升失败(Promotion Failure)
- 疏散失败(Evacuation Failure)
- 并发模式失败(Concurrent Mode Failure)
- 解决方案:
- 增加-XX:G1ReservePercent
- 降低-XX:InitiatingHeapOccupancyPercent
- 增大堆内存或减少对象分配速率
问题2:长时间并发标记
- 优化方向:
- 增加-XX:ConcGCThreads
- 检查是否有大量大对象
- 使用-XX:G1ConcRefinementThreads优化 refinement
4.3 监控与诊断
推荐工具组合:
- JDK自带工具:
- jstat -gcutil 查看各代使用率
- jcmd GC.heap_info 输出详细堆信息
- 可视化工具:
- GCViewer分析GC日志
- JVisualVM观察内存趋势
- 高级诊断:
- -XX:+G1PrintHeapRegions 打印Region分配
- -Xlog:gc+ergo*=trace 输出详细决策日志
5. G1与其他回收器对比
5.1 与CMS的对比
| 特性 | G1 | CMS |
|---|---|---|
| 算法 | 分区+标记复制/整理 | 分代+标记清除 |
| 停顿目标 | 可配置 | 不可控 |
| 内存碎片 | 无 | 可能严重 |
| 全堆回收 | 避免 | 可能触发 |
| 大堆表现 | 优秀 | 较差 |
| JDK版本 | 7+(9+默认) | 1.4-14 |
5.2 与ZGC的对比
虽然ZGC(JDK11+)在超大规模堆(TB级)表现更优,但G1在8GB-64GB堆场景仍有优势:
- 更成熟稳定
- 资源消耗更低
- 调优经验更丰富
- 兼容更老JDK版本
6. 生产环境实践心得
在实际运维中,我们发现几个关键经验:
- 避免过度调优:先使用默认参数运行,再针对性调整。曾有个案例,过度追求低停顿导致吞吐量下降60%
- 监控GC日志:建议至少保留7天日志,使用-XX:+UseGCLogFileRotation自动轮转
- 混合工作负载适配:对于突发流量场景,可设置-XX:G1PeriodicGCInterval=300000(5分钟)定期维护
- 版本升级验证:从JDK8升级到JDK11时,G1性能通常提升15-20%,但需验证大对象处理变化
一个电商平台的调优案例:
- 初始配置:32GB堆,MaxGCPauseMillis=200ms
- 问题:高峰期频繁Full GC
- 解决方案:
- 增大堆至48GB
- 设置-XX:G1NewSizePercent=30
- 添加-XX:G1MixedGCCountTarget=16
- 结果:Full GC消除,平均停顿从180ms降至120ms