1. 垃圾收集器背景与核心挑战
在Java虚拟机(JVM)的内存管理体系中,垃圾收集器(Garbage Collector)承担着自动内存回收的重任。现代JVM主要提供四种垃圾收集器:Serial、Parallel、CMS和G1。其中CMS(Concurrent Mark-Sweep)和G1(Garbage-First)因其独特的并发特性在大内存应用中广受青睐。
CMS收集器采用"标记-清除"算法,通过并发标记阶段减少停顿时间,适合追求低延迟的应用场景。但它存在内存碎片问题,且无法处理浮动垃圾。G1则采用分区域(Region)收集策略,通过可预测的停顿时间模型,兼顾吞吐量和延迟要求。
关键区别:CMS是传统分代收集器的改良版,而G1是面向服务端应用的区域性收集器,两者设计哲学存在本质差异。
2. CMS收集器工作流程拆解
2.1 初始标记阶段(Initial Mark)
这是整个CMS周期中第一个需要暂停应用线程(STW)的阶段。它仅标记GC Roots能直接关联到的对象,速度极快。在HotSpot实现中,这个阶段通常借助OopMap数据结构快速定位根引用。
// 示例:OopMap中的引用记录格式 0x00007f3e6c00f890: 0x000000076b9ff8a0 [oop(0x000000076b9ff8a0)] 0x00007f3e6c00f898: 0x000000076ba02c50 [oop(0x000000076ba02c50)]2.2 并发标记阶段(Concurrent Mark)
在此阶段,GC线程与用户线程并发执行。收集器会遍历整个老年代对象图,标记所有存活对象。由于用户线程同时运行,可能出现以下情况:
- 新创建的对象被误标为存活(浮动垃圾)
- 对象引用关系变化导致漏标(需后续重新标记处理)
2.3 重新标记阶段(Remark)
第二个STW阶段,处理并发标记期间遗漏的对象。HotSpot采用增量更新(Incremental Update)算法,通过写屏障记录引用变化。典型耗时在10-100ms之间,具体取决于堆大小和对象数量。
2.4 并发清除阶段(Concurrent Sweep)
回收未被标记的对象空间。此阶段同样与用户线程并发执行,不会导致应用停顿。但会产生内存碎片,可能触发Full GC进行压缩。
3. G1收集器核心机制解析
3.1 区域化内存布局
G1将堆划分为多个大小相等的Region(默认约2048个),每个Region可以是Eden、Survivor或Old类型。这种设计带来两个优势:
- 优先收集垃圾比例高的Region(Garbage-First原则)
- 避免全堆回收,通过多次部分回收达到全局平衡
# 查看G1区域信息的JVM参数 -XX:+PrintGCDetails -XX:+PrintRegionDistribution3.2 混合收集周期
G1的工作流程可分为以下阶段:
3.2.1 初始标记(Initial Mark)
与CMS类似,但G1会借助Young GC的STW阶段完成,减少额外停顿。该阶段会记录下SATB(Snapshot-At-The-Beginning)快照。
3.2.2 根区域扫描(Root Region Scanning)
扫描Survivor区对老年代的引用,确保并发标记的正确性。必须在下次Young GC前完成。
3.2.3 并发标记(Concurrent Mark)
与CMS的主要差异:
- 使用SATB算法处理引用变化
- 会计算各Region存活对象比例
- 识别完全空闲的Region立即回收
3.2.4 最终标记(Final Remark)
处理剩余SATB缓冲区引用,执行引用处理(如类卸载)。此阶段也是STW的。
3.2.5 清理阶段(Cleanup)
统计各Region存活对象,排序回收优先级。部分Region会在此阶段被回收。
4. 关键流程对比与可视化
4.1 CMS工作流程图
[STW] Initial Mark → [Concurrent] Marking → [STW] Remark → [Concurrent] Sweeping4.2 G1工作流程图
[STW] Initial Mark (with Young GC) ↓ [Concurrent] Root Region Scanning ↓ [Concurrent] Marking ↓ [STW] Final Remark ↓ [STW] Cleanup4.3 性能特征对比表
| 特性 | CMS | G1 |
|---|---|---|
| 算法 | 标记-清除 | 标记-整理 |
| 停顿目标 | 低延迟 | 可预测延迟 |
| 内存占用 | 较低 | 较高(约10-20%额外) |
| 适用堆大小 | <8GB | >4GB |
| Full GC触发条件 | 并发模式失败/晋升失败 | 疏散失败/内存不足 |
5. 调优实践与避坑指南
5.1 CMS常见问题
并发模式失败:当老年代空间不足以容纳晋升对象时触发Full GC。解决方案:
- 增加-XX:CMSInitiatingOccupancyFraction值(默认68%)
- 配合-XX:+UseCMSInitiatingOccupancyOnly
- 减少-XX:NewRatio增加年轻代比例
内存碎片:长期运行后老年代出现不连续空间。可通过以下参数缓解:
-XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=5
5.2 G1关键配置
- 最大停顿时间:设置合理的-XX:MaxGCPauseMillis(默认200ms)
- 区域大小:-XX:G1HeapRegionSize(1MB-32MB,2的幂次)
- 并行线程数:-XX:ParallelGCThreads和-XX:ConcGCThreads
实测建议:G1的-XX:InitiatingHeapOccupancyPercent(默认45%)需要根据对象分配速率调整,过晚启动会导致并发周期来不及完成。
6. 生产环境监控要点
6.1 CMS监控指标
- Concurrent mode failure次数
- 最后一次GC持续时间(jstat -gcutil)
- 老年代碎片率(通过GC日志计算)
6.2 G1监控重点
- Evacuation Failure发生频率
- Remembered Set大小(jcmd VM.native_memory)
- 混合收集效率(存活对象/回收区域比)
# 获取G1详细信息的命令示例 jcmd <pid> GC.heap_info jstat -gccause <pid> 1s在大型电商系统中,我们曾遇到G1的Humongous对象分配导致频繁Full GC的情况。通过设置-XX:G1HeapRegionSize=16MB并配合-XX:G1ReservePercent=15,成功将99%的GC停顿控制在150ms以内。这印证了G1设计哲学的核心——通过合理的区域划分和回收策略,在吞吐量与延迟之间取得最佳平衡。