1. G1垃圾回收器概述
G1(Garbage-First)是JDK 7中引入的服务器端垃圾回收器,它的设计目标是替代CMS回收器,成为JVM默认的垃圾回收器。与传统的分代回收器不同,G1采用了一种全新的内存布局方式——将堆内存划分为多个大小相等的Region(区域),每个Region可以是Eden区、Survivor区或Old区。
关键特性:G1是首个面向服务端应用的并发垃圾回收器,能够在不牺牲吞吐量的前提下实现可预测的停顿时间模型。
G1的核心优势在于它能够根据用户设定的停顿时间目标(通过-XX:MaxGCPauseMillis参数指定),优先回收垃圾最多的Region(这也是"Garbage-First"名称的由来)。这种设计使得G1特别适合大内存、多核处理器的现代服务器环境。
2. G1的内存布局与关键概念
2.1 Region划分
G1将堆内存划分为多个固定大小的Region(默认约2048个),每个Region的大小可以通过-XX:G1HeapRegionSize参数设置,范围从1MB到32MB。这种设计带来了几个重要特性:
- 动态角色分配:每个Region在运行时可以动态切换为Eden、Survivor或Old区,不需要像传统分代回收器那样固定划分空间比例
- 大对象处理:超过Region大小50%的对象会被视为Humongous对象,存放在专门的Humongous Region中
- 更灵活的内存管理:G1可以根据回收效率优先选择垃圾比例高的Region进行回收
2.2 关键数据结构
- Remembered Sets(RSet):每个Region都有一个RSet,用于记录其他Region中指向本Region的引用。这避免了全堆扫描,是G1实现并行回收的关键
- Collection Sets(CSet):每次GC时选择回收的Region集合,G1会根据预测模型选择最有利的Region组合
- TAMS(Top at Mark Start)指针:标记阶段开始时,G1会为每个Region设置两个TAMS指针(prevTAMS和nextTAMS),用于区分标记过程中新分配的对象
3. G1的垃圾回收过程详解
3.1 年轻代GC(Young GC)
年轻代GC是G1中最频繁发生的回收类型,主要特点包括:
- 触发条件:Eden区耗尽时触发
- 执行过程:
- 暂停所有应用线程(Stop-The-World)
- 从根对象(栈、寄存器、全局变量等)开始标记存活对象
- 将存活对象拷贝到Survivor区或晋升到Old区
- 清空Eden区和被处理的Survivor区
- 并行处理:G1会利用多核优势并行执行对象拷贝和Region清理
实战技巧:通过-XX:MaxGCPauseMillis可以调整预期的最大停顿时间,但设置过小会导致GC频率增加,反而降低吞吐量。生产环境通常建议设置在100-200ms之间。
3.2 混合GC(Mixed GC)
混合GC是G1特有的回收模式,它会同时处理年轻代和老年代Region。其核心流程包括:
初始标记阶段(Initial Mark):
- 伴随年轻代GC一起执行
- 标记从GC Roots直接可达的老年代对象
- 需要短暂STW
并发标记阶段(Concurrent Marking):
- 与应用线程并发执行
- 遍历对象图标记所有可达对象
- 处理SATB(Snapshot-At-The-Beginning)日志,记录标记过程中引用变化
最终标记阶段(Remark):
- 再次STW
- 处理剩余的SATB日志
- 执行引用处理(如类卸载、弱引用清理)
清理阶段(Cleanup):
- 统计各Region存活对象比例
- 选择回收价值高的Region加入CSet
- 完全清空不包含存活对象的Region
3.3 全堆回收(Full GC)
当G1无法满足内存需求时会退化为串行Full GC,这种情况通常表明:
- 并发标记阶段完成前老年代就被填满
- 晋升失败(没有足够的空间容纳晋升对象)
- 大对象分配失败
避坑指南:Full GC会导致长时间停顿,应通过适当增大堆内存(-Xmx)、降低晋升阈值(-XX:InitiatingHeapOccupancyPercent)或优化对象分配模式来避免。
4. G1的核心算法与优化技术
4.1 并发标记的实现
G1的并发标记阶段采用了以下关键技术:
- 三色标记算法:将对象分为白(未访问)、灰(已访问但子节点未处理)、黑(已完全处理)三种状态
- SATB(Snapshot-At-The-Beginning):在标记开始时创建对象图快照,确保标记一致性
- 预写屏障(Write Barrier):在对象引用修改时记录变更,保证并发标记的正确性
4.2 停顿预测模型
G1通过历史数据建立停顿预测模型,主要考虑:
- Region回收时间:基于过去回收相同类型Region的耗时
- 对象拷贝成本:估算存活对象数量和拷贝开销
- 记忆集处理时间:RSet扫描和更新的时间成本
4.3 记忆集优化
G1对RSet进行了多项优化:
- 稀疏表结构:对小规模引用使用数组存储,大规模引用转为哈希表
- 并行处理:多个GC线程同时处理不同Region的RSet
- 增量更新:在并发标记阶段只记录变更,不立即处理
5. G1调优实践与常见问题
5.1 关键参数配置
| 参数 | 说明 | 推荐值 |
|---|---|---|
| -XX:+UseG1GC | 启用G1回收器 | 必须 |
| -XX:MaxGCPauseMillis | 目标最大停顿时间 | 100-200ms |
| -XX:InitiatingHeapOccupancyPercent | 触发并发标记的堆占用率 | 45% |
| -XX:G1HeapRegionSize | Region大小 | 根据堆大小自动计算 |
| -XX:G1NewSizePercent | 年轻代最小占比 | 5% |
| -XX:G1MaxNewSizePercent | 年轻代最大占比 | 60% |
5.2 常见问题排查
频繁Full GC
- 检查IHOP设置是否合理(-XX:InitiatingHeapOccupancyPercent)
- 分析对象晋升模式,可能存在大对象或过早晋升
- 考虑增加堆大小或调整Region大小
长时间并发标记
- 检查CPU资源是否充足
- 评估-XX:ConcGCThreads设置是否合理
- 分析对象图复杂度,可能存在深层次引用链
记忆集占用过高
- 检查跨Region引用模式
- 考虑使用-XX:G1RSetUpdatingPauseTimePercent限制RSet更新时间
- 评估是否需要增大Region大小
5.3 监控与诊断工具
- GC日志分析:添加-XX:+PrintGCDetails -Xloggc:gc.log参数
- VisualVM/JConsole:实时监控堆使用和GC活动
- jstat工具:查看各代使用率和GC时间统计
- Eclipse Memory Analyzer:分析堆转储文件
6. G1与其他回收器的对比
6.1 与CMS的对比
| 特性 | G1 | CMS |
|---|---|---|
| 内存布局 | 分Region | 传统分代 |
| 回收算法 | 标记-整理 | 标记-清除 |
| 停顿目标 | 可配置 | 不可控 |
| 全堆回收 | 有退化可能 | 更频繁 |
| 大堆适用性 | 更适合 | 有限制 |
6.2 与ZGC/Shenandoah的对比
新一代低延迟回收器在某些方面表现更好,但G1仍有其优势:
- 更成熟的实现,长期生产验证
- 对超大堆(>4TB)的支持更好
- 资源消耗(CPU/内存)相对较低
- JDK 8等老版本中的最佳选择
7. 生产环境最佳实践
经过多年实战检验,以下G1使用建议值得参考:
堆大小设置
- 建议至少6GB以上,太小会限制G1优势发挥
- 避免设置过大导致回收效率下降(通常不超过32GB)
停顿时间目标
- 初次设置可从200ms开始,逐步优化
- 不要设置过小(<50ms)导致GC频率激增
监控与调整
- 定期分析GC日志,观察趋势变化
- 关注老年代占用率和晋升速率
- 根据实际表现微调IHOP等参数
应用配合
- 减少大对象分配
- 优化对象生命周期
- 避免频繁创建短命大对象
在实际项目中,我曾遇到一个典型案例:某电商系统在促销期间频繁发生Full GC。通过分析发现是商品详情页缓存对象过早晋升导致的。解决方案是调整缓存对象的存活时间阈值,同时适当增加-XX:InitiatingHeapOccupancyPercent值,最终将系统停顿时间降低了60%。