问题
G1和CMS有什么区别?
答案
核心概念
G1(Garbage-First)和CMS(Concurrent Mark Sweep)都是面向老年代的并发垃圾回收器,但它们在设计理念、内存布局和回收策略上有本质区别。G1是CMS的继任者,旨在解决CMS的主要问题,同时保持低延迟的特性。
内存布局差异
1. CMS的内存布局
public class CMSMemoryLayout {
/*
* CMS内存布局:
* ┌─────────────────────────────────────────────────────────┐
* │ 新生代 │
* │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
* │ │ Eden │ │ Survivor│ │ Survivor│ │
* │ │ 区 │ │ 0 │ │ 1 │ │
* │ └─────────┘ └─────────┘ └─────────┘ │
* └─────────────────────────────────────────────────────────┘
* ┌─────────────────────────────────────────────────────────┐
* │ 老年代 │
* │ (连续的大块空间) │
* └─────────────────────────────────────────────────────────┘
*/
public static void demonstrateCMSLayout() {
// CMS采用传统的分代布局
// 新生代:Eden + 2个Survivor区
// 老年代:连续的大块内存空间
System.out.println("CMS:传统的分代内存布局");
System.out.println("新生代:Eden + S0 + S1");
System.out.println("老年代:连续内存区域");
}
}
2. G1的内存布局
public class G1MemoryLayout {
/*
* G1内存布局:
* ┌─────────────────────────────────────────────────────────┐
* │ Region划分 │
* │ ┌──���──┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
* │ │ E │ │ E │ │ O │ │ H │ │ E │ │ O │ │
* │ │ │ │ │ │ │ │ │ │ │ │ │ │
* │ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ │
* │ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
* │ │ O │ │ S │ │ S │ │ E │ │ O │ │ E │ │
* │ │ │ │ │ │ │ │ │ │ │ │ │ │
* │ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ │
* └─────────────────────────────────────────────────────────┘
* E: Eden Region, O: Old Region, S: Survivor Region, H: Humongous Region
*/
public static void demonstrateG1Layout() {
// G1将整个堆划分为多个大小相等的Region
// 每个Region可以是Eden、Survivor、Old或Humongous
// Region大小通常是1MB到32MB之间
System.out.println("G1:基于Region的内存布局");
System.out.println("堆被划分为多个Region");
System.out.println("每个Region可以扮演不同角色");
}
}
回收算法差异
1. CMS的回收算法
public class CMSAlgorithm {
// CMS使用标记-清除算法
public static void markSweepAlgorithm() {
/*
* 标记-清除算法:
* 1. 标记阶段:标记所有存活对象
* 2. 清除阶段:回收未标记的对象
*
* 优点:
* - 算法简单,实现容易
* - 回收效率高
*
* 缺点:
* - 产生内存碎片
* - 标记和清除都需要遍历整个内存空间
*/
// 内存碎片示例
// 回收前:[存活][垃圾][存活][垃圾][存活]
// 回收后:[空闲][存活][空闲][存活][空闲]
// 问题:虽然总空闲空间很大,但无法分配连续的大对象
}
}
2. G1的回收算法
public class G1Algorithm {
// G1使用标记-整理算法(基于Region)
public static void markCompactAlgorithm() {
/*
* G1的回收过程:
* 1. 标记阶段:并发标记存活对象
* 2. 回收阶段:选择收益最高的Region进行回收
* 3. 整理阶段:将存活对象复制到新的Region
*
* 特点:
* - 增量回收,每次只回收部分Region
* - 复制算法,无内存碎片
* - 可预测的停顿时间
*/
// Region回收示例
// 回收前:Region1(垃圾) Region2(存活) Region3(垃圾)
// 回收后:Region1(空闲) Region2(存活) Region3(空闲)
// 结果:无碎片,内存利用率高
}
}
回收过程差异
1. CMS的回收过程
public class CMSProcess {
public static void cmsCollectionProcess() {
/*
* CMS回收过程:
* 1. 初始标记 (STW) - 标记GC Roots直接引用的对象
* 2. 并发标记 - 与应用并发,标记所有存活对象
* 3. 重新标记 (STW) - 处理并发期间的引用变化
* 4. 并发清除 - 与应用并发,回收垃圾对象
*/
System.out.println("CMS:四个阶段,两次STW");
System.out.println("STW时间:初始标记(短) + 重新标记(中等)");
System.out.println("并发时间:标记(长) + 清除(长)");
}
}
2. G1的回收过程
public class G1Process {
public static void g1CollectionProcess() {
/*
* G1回收过程:
* 1. 初始标记 (STW) - 标记GC Roots直接引用的对象
* 2. 并发标记 - 与应用并发,建立Region存活信息
* 3. 最终标记 (STW) - 完成存活对象标记
* 4. 筛选回收 (STW) - 选择Region进行回收和复制
*/
System.out.println("G1:三个主要阶段,多次STW");
System.out.println("STW时间:Young GC(短) + Mixed GC(中等)");
System.out.println("并发时间:标记过程(长)");
}
}
性能特点对比
1. 内存碎片处理
public class MemoryFragmentationComparison {
public static void compareFragmentation() {
// CMS的内存碎片问题
demonstrateCMSFragmentation();
// G1的内存整理能力
demonstrateG1Compaction();
}
private static void demonstrateCMSFragmentation() {
/*
* CMS内存碎片问题:
* - 使用标记-清除算法,不移动对象
* - 随着时间推移,内存碎片越来越多
* - 可能无法分配大对象,触发Full GC
* - Full GC会导致长时间停顿
*/
// 示例:CMS碎片化场景
// 老年代空间:[占用10MB][空闲1MB][占用10MB][空闲1MB][占用10MB]
// 需要分配:8MB对象
// 结果:分配失败,虽然总空闲空间为2MB > 8MB
}
private static void demonstrateG1Compaction() {
/*
* G1内存整理能力:
* - 使用复制算法,自动整理内存
* - 每次回收都选择完整的Region进行清理
* - 将存活对象复制到新的Region
* - 从根本上避免内存碎片
*/
// 示例:G1内存整理
// 回收前:Region1(碎片化) Region2(碎片化)
// 回收后:Region1(空) Region2(整理后,内存连续)
}
}
2. 停顿时间控制
public class PauseTimeComparison {
public static void comparePauseTimes() {
// CMS的停顿时间特点
demonstrateCMSPauseTimes();
// G1的停顿时间控制
demonstrateG1PauseTimeControl();
}
private static void demonstrateCMSPauseTimes() {
/*
* CMS停顿时间:
* - Young GC:可预测,较短(几十毫秒)
* - CMS GC:初始标记(短) + 重新标记(中等)
* - Full GC:不可预测,可能很长(数秒)
* - 问题:Full GC的停顿时间不可控
*/
System.out.println("CMS停顿时间特点:");
System.out.println("优点:大部分停顿时间较短");
System.out.println("缺点:Full GC停顿时间不可控");
}
private static void demonstrateG1PauseTimeControl() {
/*
* G1停顿时间控制:
* - 可通过参数设置最大停顿时间目标
* - -XX:MaxGCPauseMillis=200 (200ms)
* - G1会根据停顿目标调整回收策略
* - 增量回收,避免长时间停顿
*/
System.out.println("G1停顿时间特点:");
System.out.println("优点:停顿时间可预测、可控");
System.out.println("特点:增量回收,分批处理");
}
}
大对象处理差异
1. CMS的大对象处理
public class CMSLargeObjectHandling {
public static void demonstrateCMSLargeObjects() {
/*
* CMS大对象处理:
* - 大对象直接在老年代分配
* - 如果老年代空间不足且碎片化,可能分配失败
* - 分配失败会触发Full GC进行内存整理
* - Full GC会导致长时间停顿
*/
// 示例:CMS大对象分配
allocateLargeObject(); // 可能触发Full GC
}
private static void allocateLargeObject() {
// 8MB大对象
byte[] largeArray = new byte[8 * 1024 * 1024];
// 在CMS中,如果老年代碎片化,可能无法分配
}
}
2. G1的大对象处理
public class G1LargeObjectHandling {
public static void demonstrateG1LargeObjects() {
/*
* G1大对象处理:
* - 大对象分配在Humongous Region中
* - 一个大对象可能占用多个连续的Region
* - 回收时会考虑Humongous Region的回收收益
* - 避免了老年代的碎片化问题
*/
// 示例:G1大对象分配
allocateHumongousObject(); // 分配到Humongous Region
}
private static void allocateHumongousObject() {
// 8MB大对象,假设Region大小为1MB
byte[] largeArray = new byte[8 * 1024 * 1024];
// 会占用8个连续的Humongous Region
}
}
适用场景对比
1. CMS适用场景
public class CMSSuitableScenarios {
/*
* CMS适用场景:
* 1. 对响应时间敏感的应用
* 2. 内存占用较大的应用
* 3. CPU资源充足的环境
* 4. Java 8及之前的版本
*/
public static void cmsBestPractices() {
// 适合互联网Web应用
WebApplication webApp = new WebApplication();
// 适合缓存系统
CacheSystem cache = new CacheSystem();
// 需要配置合理的参数避免Full GC
configureCMSParameters();
}
private static void configureCMSParameters() {
// -XX:CMSInitiatingOccupancyFraction=70
// -XX:+UseCMSInitiatingOccupancyOnly
// -XX:+CMSParallelRemarkEnabled
}
}
2. G1适用场景
public class G1SuitableScenarios {
/*
* G1适用场景:
* 1. 需要可预测停顿时间的应用
* 2. 内存占用较大的应用(>4GB)
* 3. 大内存服务器应用
* 4. Java 9+的推荐选择
*/
public static void g1BestPractices() {
// 适合大数据处理应用
BigDataApplication bigDataApp = new BigDataApplication();
// 适合微服务架构
MicroService microservice = new MicroService();
// 需要配置停顿时间目标
configureG1Parameters();
}
private static void configureG1Parameters() {
// -XX:MaxGCPauseMillis=200
// -XX:G1HeapRegionSize=16m
// -XX:InitiatingHeapOccupancyPercent=45
}
}
配置参数对比
1. CMS关键参数
# 启用CMS
-XX:+UseConcMarkSweepGC
# CMS触发阈值
-XX:CMSInitiatingOccupancyFraction=70
-XX:+UseCMSInitiatingOccupancyOnly
# 并发参数
-XX:ParallelGCThreads=4
-XX:ConcGCThreads=2
# 内存整理
-XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction=5
2. G1关键参数
# 启用G1
-XX:+UseG1GC
# 停顿时间目标
-XX:MaxGCPauseMillis=200
# Region大小
-XX:G1HeapRegionSize=16m
# 触发阈值
-XX:InitiatingHeapOccupancyPercent=45
# 大对象阈值
-XX:G1HumongousObjectThreshold=2m
迁移建议
1. 从CMS迁移到G1
public class CMS2G1Migration {
public static void migrationSteps() {
/*
* 迁移步骤:
* 1. 评估当前应用特点
* 2. 在测试环境中验证G1性能
* 3. 调整G1参数以匹配性能要求
* 4. 逐步在生产环境中部署
*/
System.out.println("迁移建议:");
System.out.println("1. Java 9+环境推荐使用G1");
System.out.println("2. 大内存应用(>8GB)优先选择G1");
System.out.println("3. 需要可预测停顿时间选择G1");
System.out.println("4. 传统应用可继续使用CMS");
}
}
面试要点总结
- 内存布局:CMS传统分代 vs G1基于Region
- 回收算法:CMS标记-清除 vs G1标记-整理(复制)
- 碎片处理:CMS有碎片问题 vs G1自动整理
- 停顿控制:CMS不可控 vs G1可预测
- 大对象:CMS老年代 vs G1 Humongous Region
- 适用场景:CMS响应敏感 vs G1大内存+可预测停顿
关键理解:
- G1是CMS的进化版本,解决了CMS的主要问题
- Region化设计是G1的核心创新
- 可预测停顿时间是G1的最大优势
- 从CMS到G1是Java GC发展的趋势
选择建议:
- Java 9+:优先选择G1
- 大内存应用(>4GB):选择G1
- 需要可预测停顿时间:选择G1
- 传统小内存应用:可考虑CMS
- 重点关注应用的性能需求和JVM版本
G1代表了现代垃圾回收器的发展方向,是理解Java GC演进的重要知识点。