Appearance
G1 GC(垃圾优先垃圾回收器)
概述
G1(Garbage First)GC 是一个面向服务端应用的垃圾回收器,设计目标是在保持高吞吐量的同时实现低延迟。G1 GC 采用分区域的内存管理方式,将堆内存划分为多个大小相等的区域(Region),并通过优先回收垃圾最多的区域来获得最大的回收效益。从 JDK 9 开始,G1 GC 成为默认的垃圾回收器。
基本特征
核心特点
- 区域化管理:将堆内存划分为多个独立的区域
- 可预测停顿:可以设置期望的最大停顿时间
- 并发回收:大部分回收工作与应用线程并发执行
- 增量回收:每次只回收部分区域,避免长时间停顿
- 自适应调节:根据历史数据动态调整回收策略
适用场景
- 大内存应用:堆内存 4GB 以上
- 延迟敏感服务:要求可预测的停顿时间
- 高吞吐量应用:需要平衡吞吐量和延迟
- 现代服务架构:微服务、云原生应用
内存布局
G1 GC 将堆内存划分为多个大小相等的区域(Region):
G1 GC 堆内存布局:
┌─────────────────────────────────────────────────────────────┐
│ G1 堆内存 │
│ ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐ │
│ │ E │ E │ S │ O │ O │ H │ E │ S │ O │ O │ │
│ └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ │
│ ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐ │
│ │ O │ F │ F │ E │ E │ S │ O │ H │ F │ F │ │
│ └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ │
└─────────────────────────────────────────────────────────────┘
区域类型说明:
E = Eden 区域(新生代)
S = Survivor 区域(新生代)
O = Old 区域(老年代)
H = Humongous 区域(大对象)
F = Free 区域(空闲)
区域特点:
- 每个区域大小:1MB - 32MB(通常 1MB-2MB)
- 区域总数:通常 2048 个左右
- 动态分配:区域类型可以动态改变
- 大对象:超过区域大小 50% 的对象存储在 Humongous 区域
区域管理
G1 区域管理详细说明:
1. Eden 区域:
- 新对象分配的区域
- 当 Eden 区域满时触发 Minor GC
- 回收后变为 Free 区域
2. Survivor 区域:
- 存储从 Eden 区域存活下来的对象
- 分为 From 和 To 两个逻辑区域
- 对象在 Survivor 区域间复制
3. Old 区域:
- 存储长期存活的对象
- 从 Survivor 区域晋升的对象
- 大对象可能直接分配到此
4. Humongous 区域:
- 存储大对象(> 区域大小的 50%)
- 可能占用连续的多个区域
- 在 Mixed GC 中回收
5. Free 区域:
- 空闲区域,可以分配给任何类型
- 动态分配给 Eden、Survivor 或 Old
- 提供内存分配的灵活性
工作流程
G1 GC 有多种回收模式,根据堆内存使用情况自动选择:
1. Young GC(新生代回收)
Young GC 详细流程:
1. 触发条件:
- Eden 区域分配满
- 新对象无法分配到 Eden 区域
- 定期检查是否需要回收
2. 回收过程:
a) 暂停应用线程(STW)
b) 扫描 GC Roots 和跨区域引用
c) 复制存活对象到 Survivor 或 Old 区域
d) 更新对象年龄和引用关系
e) 清空 Eden 区域,标记为 Free
f) 恢复应用线程
3. 对象处理:
- 新分配对象:回收(变为垃圾)
- 存活对象:复制到 Survivor 区域
- 老对象:年龄增加,可能晋升到 Old 区域
- 大对象:直接分配到 Humongous 区域
4. 区域状态变化:
- Eden 区域 → Free 区域
- 部分 Free 区域 → Survivor 区域
- 可能分配新的 Old 区域
2. Concurrent Marking(并发标记)
并发标记详细流程:
1. 触发条件:
- 堆内存使用率达到阈值(默认 45%)
- 上次并发标记完成后的时间间隔
- 预测即将需要 Mixed GC
2. 标记阶段:
a) 初始标记(Initial Mark)- STW
- 标记 GC Roots 直接引用的对象
- 通常与 Young GC 一起执行
- 停顿时间很短
b) 根区域扫描(Root Region Scan)- 并发
- 扫描 Survivor 区域中指向老年代的引用
- 与应用线程并发执行
- 必须在下次 Young GC 前完成
c) 并发标记(Concurrent Mark)- 并发
- 遍历整个对象图,标记存活对象
- 使用三色标记算法
- 与应用线程并发执行
d) 重新标记(Remark)- STW
- 处理并发标记期间的引用变化
- 使用 SATB(Snapshot At The Beginning)算法
- 完成最终的标记工作
e) 清理(Cleanup)- STW
- 统计每个区域的存活对象
- 回收完全空的区域
- 为 Mixed GC 做准备
3. 标记结果:
- 确定每个区域的存活对象数量
- 计算回收效益(垃圾对象数量)
- 为 Mixed GC 选择回收区域
3. Mixed GC(混合回收)
txt
Mixed GC 详细流程:
1. 触发条件:
- 并发标记完成
- 老年代区域中有足够的垃圾
- 预测回收时间在目标停顿时间内
2. 回收策略:
- 回收所有 Eden 区域
- 回收所有 Survivor 区域
- 回收部分 Old 区域(垃圾最多的)
- 回收 Humongous 区域(如果需要)
3. 回收过程:
a) 暂停应用线程(STW)
b) 选择回收区域集合(Collection Set)
c) 扫描 GC Roots 和跨区域引用
d) 并行复制存活对象到新区域
e) 更新引用关系和记忆集
f) 清空回收区域,标记为 Free
g) 恢复应用线程
4. 区域选择算法:
- 计算每个区域的回收效益
- 优先选择垃圾最多的区域
- 考虑停顿时间目标
- 平衡回收效果和停顿时间
4. Full GC(完整回收)
Full GC 详细流程:
1. 触发条件:
- 堆内存严重不足
- Mixed GC 无法释放足够空间
- 大对象分配失败
- 元空间不足(JDK 8+)
2. 回收过程:
- 使用单线程 Serial Old 算法
- 标记-整理算法
- 长时间停顿(秒级)
- 回收整个堆内存
3. 避免策略:
- 合理设置堆大小
- 调整并发标记阈值
- 优化 Mixed GC 参数
- 监控内存使用情况
算法实现
G1 回收算法
java
// G1 垃圾回收器主要算法实现
public class G1Collector {
private final RegionManager regionManager;
private final ConcurrentMarkingThread markingThread;
private final CollectionSetChooser csChooser;
private final RememberedSetManager rsManager;
// G1 回收的主要入口
public void collect(GCCause cause) {
switch (cause) {
case ALLOCATION_FAILURE:
youngGC();
break;
case G1_HUMONGOUS_ALLOCATION:
if (shouldStartConcurrentMark()) {
startConcurrentMark();
}
youngGC();
break;
case G1_PERIODIC_COLLECTION:
periodicGC();
break;
default:
fullGC();
}
}
// Young GC 实现
private void youngGC() {
// 1. 暂停应用线程
stopAllApplicationThreads();
try {
// 2. 选择回收集合
CollectionSet cs = selectYoungCollectionSet();
// 3. 扫描根对象
RootScanner rootScanner = new RootScanner();
rootScanner.scanRoots(cs);
// 4. 并行复制对象
ParallelObjectCopier copier = new ParallelObjectCopier();
copier.copyObjects(cs);
// 5. 更新引用和记忆集
updateReferencesAndRememberedSets(cs);
// 6. 释放回收区域
regionManager.freeRegions(cs.getRegions());
// 7. 更新统计信息
updateGCStatistics();
} finally {
// 8. 恢复应用线程
resumeAllApplicationThreads();
}
}
// 并发标记实现
private void startConcurrentMark() {
markingThread.startMarking();
}
// Mixed GC 实现
private void mixedGC() {
stopAllApplicationThreads();
try {
// 1. 选择混合回收集合
CollectionSet cs = selectMixedCollectionSet();
// 2. 扫描根对象和跨区域引用
scanRootsAndRememberedSets(cs);
// 3. 并行复制存活对象
parallelCopyLiveObjects(cs);
// 4. 更新引用关系
updateReferences(cs);
// 5. 清理回收区域
cleanupCollectedRegions(cs);
} finally {
resumeAllApplicationThreads();
}
}
// 选择回收集合
private CollectionSet selectMixedCollectionSet() {
CollectionSet cs = new CollectionSet();
// 添加所有 Eden 区域
cs.addAll(regionManager.getEdenRegions());
// 添加所有 Survivor 区域
cs.addAll(regionManager.getSurvivorRegions());
// 选择垃圾最多的 Old 区域
List<Region> oldRegions = csChooser.chooseOldRegions(
pauseTimeTarget,
regionManager.getOldRegions()
);
cs.addAll(oldRegions);
return cs;
}
}
并发标记算法
java
// G1 并发标记算法实现
public class G1ConcurrentMarking {
private final MarkBitMap markBitMap;
private final SATBMarkQueue satbQueue;
private final RegionManager regionManager;
public void concurrentMark() {
try {
// 1. 初始标记(与 Young GC 一起执行)
initialMark();
// 2. 根区域扫描
rootRegionScan();
// 3. 并发标记
concurrentMarkPhase();
// 4. 重新标记
remark();
// 5. 清理
cleanup();
} catch (InterruptedException e) {
// 处理中断
Thread.currentThread().interrupt();
}
}
private void initialMark() {
// 在 Young GC 的 STW 期间执行
// 标记 GC Roots 直接引用的对象
for (GCRoot root : getGCRoots()) {
Object obj = root.getObject();
if (obj != null) {
markBitMap.mark(obj);
satbQueue.enqueue(obj);
}
}
}
private void rootRegionScan() {
// 扫描 Survivor 区域中指向老年代的引用
for (Region region : regionManager.getSurvivorRegions()) {
scanRegionForOldReferences(region);
}
}
private void concurrentMarkPhase() {
// 并发标记阶段
while (!satbQueue.isEmpty()) {
Object obj = satbQueue.dequeue();
if (obj != null && markBitMap.isMarked(obj)) {
// 扫描对象的引用
for (Object ref : obj.getReferences()) {
if (ref != null && !markBitMap.isMarked(ref)) {
if (markBitMap.mark(ref)) {
satbQueue.enqueue(ref);
}
}
}
}
// 检查是否需要让出 CPU
if (shouldYield()) {
Thread.yield();
}
}
}
private void remark() {
// STW 阶段,处理并发标记期间的变化
stopAllApplicationThreads();
try {
// 处理 SATB 缓冲区
processSATBBuffers();
// 完成剩余的标记工作
finishMarking();
} finally {
resumeAllApplicationThreads();
}
}
private void cleanup() {
stopAllApplicationThreads();
try {
// 统计每个区域的存活对象
for (Region region : regionManager.getAllRegions()) {
int liveObjects = countLiveObjects(region);
region.setLiveObjectCount(liveObjects);
// 完全空的区域直接回收
if (liveObjects == 0) {
regionManager.freeRegion(region);
}
}
// 为 Mixed GC 准备候选区域
prepareForMixedGC();
} finally {
resumeAllApplicationThreads();
}
}
}
SATB 算法实现
java
// SATB(Snapshot At The Beginning)算法实现
public class SATBAlgorithm {
private final SATBMarkQueue globalQueue;
private final ThreadLocal<SATBMarkQueue> localQueues;
// SATB 写屏障
public void satbWriteBarrier(Object from, Object to, int fieldOffset) {
// 获取原始引用值
Object oldValue = from.getReference(fieldOffset);
// 执行原始的引用写入
from.setReference(fieldOffset, to);
// SATB 处理:记录被覆盖的引用
if (isConcurrentMarkingActive() && oldValue != null) {
// 将被覆盖的引用加入 SATB 队列
SATBMarkQueue localQueue = localQueues.get();
if (localQueue.isFull()) {
// 本地队列满,转移到全局队列
globalQueue.addAll(localQueue.drain());
}
localQueue.enqueue(oldValue);
}
// 跨区域引用处理
if (from.getRegion() != to.getRegion()) {
updateRememberedSet(from, to);
}
}
// 处理 SATB 队列
public void processSATBQueues() {
// 处理全局队列
while (!globalQueue.isEmpty()) {
Object obj = globalQueue.dequeue();
if (obj != null && !markBitMap.isMarked(obj)) {
markBitMap.mark(obj);
scanObject(obj);
}
}
// 处理所有线程的本地队列
for (Thread thread : getAllApplicationThreads()) {
SATBMarkQueue localQueue = localQueues.get();
while (!localQueue.isEmpty()) {
Object obj = localQueue.dequeue();
if (obj != null && !markBitMap.isMarked(obj)) {
markBitMap.mark(obj);
scanObject(obj);
}
}
}
}
}
记忆集(Remembered Set)实现
java
// G1 记忆集实现
public class G1RememberedSet {
private final Map<Region, Set<CardIndex>> rememberedSets;
private final CardTable cardTable;
// 更新记忆集
public void updateRememberedSet(Object from, Object to) {
Region fromRegion = from.getRegion();
Region toRegion = to.getRegion();
if (fromRegion != toRegion) {
// 跨区域引用,更新目标区域的记忆集
CardIndex cardIndex = cardTable.getCardIndex(from.getAddress());
Set<CardIndex> rs = rememberedSets.get(toRegion);
if (rs == null) {
rs = new ConcurrentHashMap<>();
rememberedSets.put(toRegion, rs);
}
rs.add(cardIndex);
// 标记卡片为脏
cardTable.markCardDirty(cardIndex);
}
}
// 扫描记忆集
public void scanRememberedSet(Region region, ObjectVisitor visitor) {
Set<CardIndex> rs = rememberedSets.get(region);
if (rs != null) {
for (CardIndex cardIndex : rs) {
if (cardTable.isCardDirty(cardIndex)) {
// 扫描脏卡片中的对象
scanCard(cardIndex, visitor);
// 清理卡片
cardTable.clearCard(cardIndex);
}
}
}
}
private void scanCard(CardIndex cardIndex, ObjectVisitor visitor) {
Address cardStart = cardTable.getCardAddress(cardIndex);
Address cardEnd = cardStart.plus(CARD_SIZE);
// 遍历卡片中的所有对象
for (Address addr = cardStart; addr.lessThan(cardEnd); ) {
Object obj = getObjectAt(addr);
if (obj != null) {
visitor.visit(obj);
addr = addr.plus(obj.getSize());
} else {
break;
}
}
}
}
性能特征
优势
可预测停顿:
- 可以设置最大停顿时间目标
- 通过增量回收控制停顿时间
- 适合延迟敏感应用
高吞吐量:
- 并发回收减少应用线程停顿
- 并行回收提高回收效率
- 适合高吞吐量应用
内存效率:
- 区域化管理减少内存碎片
- 增量回收避免长时间停顿
- 自适应调节优化性能
可扩展性:
- 支持大内存堆(64GB+)
- 并行度随 CPU 核数扩展
- 适合现代多核服务器
劣势
内存开销:
- 记忆集占用额外内存(约 10-20%)
- 标记位图占用内存
- 区域管理元数据开销
复杂性:
- 算法复杂,调优参数多
- 问题诊断相对困难
- 需要深入理解原理
小堆性能:
- 小于 4GB 堆内存性能不如 Parallel GC
- 固定开销相对较大
- 不适合内存受限环境
JVM 参数配置
基础参数
bash
# 启用 G1 GC
-XX:+UseG1GC # 启用 G1 垃圾回收器
# 堆内存设置
-Xms8g # 初始堆大小
-Xmx32g # 最大堆大小
# 停顿时间目标
-XX:MaxGCPauseMillis=200 # 最大停顿时间目标(毫秒)
# 区域大小
-XX:G1HeapRegionSize=16m # 区域大小(1MB-32MB)
G1 核心参数
bash
# 并发标记相关
-XX:G1MixedGCCountTarget=8 # Mixed GC 目标次数
-XX:G1MixedGCLiveThresholdPercent=85 # Mixed GC 存活对象阈值
-XX:G1OldCSetRegionThresholdPercent=10 # 老年代回收区域比例上限
# 并发线程数
-XX:ConcGCThreads=8 # 并发 GC 线程数
-XX:ParallelGCThreads=16 # 并行 GC 线程数
# 触发条件
-XX:G1HeapWastePercent=5 # 堆浪费百分比
-XX:G1ReservePercent=10 # 堆保留百分比
内存管理参数
bash
# 大对象处理
-XX:G1HeapRegionSize=32m # 增大区域大小处理大对象
# 新生代设置
-XX:G1NewSizePercent=20 # 新生代最小比例
-XX:G1MaxNewSizePercent=40 # 新生代最大比例
# 并发标记阈值
-XX:G1ConcRefinementThreads=8 # 并发优化线程数
高级参数
bash
# 字符串去重
-XX:+UseStringDeduplication # 启用字符串去重
# 类卸载
-XX:+ClassUnloadingWithConcurrentMark # 并发标记时卸载类
# 调试参数
-XX:+PrintGCDetails # 打印详细 GC 信息
-XX:+PrintG1PrintRegionRememberedSetInfo # 打印记忆集信息
-XX:+TraceClassUnloading # 跟踪类卸载
监控与调优
关键指标
停顿时间指标:
- 平均停顿时间
- 最大停顿时间
- 停顿时间分布
吞吐量指标:
- GC 时间占比
- 应用运行时间占比
- 每秒处理请求数
内存指标:
- 堆内存使用率
- 区域分配情况
- Mixed GC 频率
调优策略
停顿时间调优
bash
# 减少停顿时间
-XX:MaxGCPauseMillis=100 # 降低停顿时间目标
-XX:G1MixedGCCountTarget=16 # 增加 Mixed GC 次数
-XX:G1OldCSetRegionThresholdPercent=5 # 减少单次回收区域
# 增加并行度
-XX:ParallelGCThreads=20 # 增加并行线程数
吞吐量调优
bash
# 提高吞吐量
-XX:MaxGCPauseMillis=500 # 允许更长停顿时间
-XX:G1MixedGCCountTarget=4 # 减少 Mixed GC 次数
-XX:G1OldCSetRegionThresholdPercent=20 # 增加单次回收区域
# 减少 GC 频率
-XX:G1HeapWastePercent=10 # 允许更多堆浪费
内存调优
bash
# 大对象优化
-XX:G1HeapRegionSize=32m # 增大区域大小
# 新生代优化
-XX:G1NewSizePercent=30 # 增加新生代比例
-XX:G1MaxNewSizePercent=50 # 允许更大新生代
# 并发标记优化
-XX:ConcGCThreads=12 # 增加并发线程数
监控工具
JVM 内置工具
bash
# 查看 G1 统计
jstat -gc <pid> 1s
# 查看 G1 详细信息
jstat -gccapacity <pid>
# 查看区域信息
jcmd <pid> GC.run_finalization
# 查看字符串去重统计
jcmd <pid> VM.stringdedup
GC 日志分析
bash
# 启用详细 G1 日志
-XX:+PrintGCDetails \
-XX:+PrintGCTimeStamps \
-XX:+PrintGCApplicationStoppedTime \
-XX:+PrintG1PrintRegionRememberedSetInfo \
-Xloggc:g1-gc.log
# 关注的日志信息:
# - [GC pause (young)]: Young GC 信息
# - [GC pause (mixed)]: Mixed GC 信息
# - [GC concurrent-mark-start]: 并发标记开始
# - [GC concurrent-mark-end]: 并发标记结束
典型应用场景
1. 大内存 Web 应用
bash
# 大内存 Web 服务配置
-XX:+UseG1GC
-Xms16g -Xmx64g
-XX:MaxGCPauseMillis=200
-XX:G1HeapRegionSize=32m
-XX:G1NewSizePercent=25
-XX:G1MaxNewSizePercent=40
-XX:ParallelGCThreads=16
-XX:ConcGCThreads=8
2. 微服务架构
bash
# 微服务应用配置
-XX:+UseG1GC
-Xms8g -Xmx32g
-XX:MaxGCPauseMillis=100
-XX:G1HeapRegionSize=16m
-XX:G1MixedGCCountTarget=12
-XX:+UseStringDeduplication
-XX:+ClassUnloadingWithConcurrentMark
3. 数据处理服务
bash
# 数据处理服务配置
-XX:+UseG1GC
-Xms32g -Xmx128g
-XX:MaxGCPauseMillis=500
-XX:G1HeapRegionSize=32m
-XX:G1NewSizePercent=30
-XX:G1HeapWastePercent=10
-XX:ParallelGCThreads=32
与其他 GC 对比
特性 | G1 GC | CMS GC | Parallel GC | ZGC |
---|---|---|---|---|
停顿时间 | 可控 | 短 | 长 | 极短 |
吞吐量 | 高 | 中等 | 最高 | 中等 |
内存开销 | 中高 | 中等 | 低 | 高 |
内存碎片 | 少 | 有 | 无 | 无 |
适用堆大小 | 4GB-64GB | 4GB-32GB | < 32GB | > 64GB |
并发程度 | 部分 | 高 | 无 | 高 |
可预测性 | 高 | 中等 | 低 | 高 |
复杂度 | 中等 | 高 | 简单 | 高 |
最佳实践
1. 选择标准
- 堆内存大小:4GB 以上推荐使用 G1
- 延迟要求:需要可预测的停顿时间
- 吞吐量要求:需要平衡吞吐量和延迟
- 应用特性:现代服务端应用
2. 配置建议
- 合理设置停顿时间目标:不要设置过于激进的目标
- 适当调整区域大小:根据对象大小分布调整
- 监控 Mixed GC 频率:避免过于频繁的 Mixed GC
- 启用有用的特性:字符串去重、类卸载等
3. 注意事项
- 避免频繁 Full GC:监控并调整参数
- 关注内存开销:记忆集和元数据开销
- 定期性能测试:验证调优效果
- 监控应用指标:不只关注 GC 指标
4. 迁移建议
- 从 CMS 迁移:当需要更好的可预测性时
- 从 Parallel 迁移:当延迟成为瓶颈时
- 迁移到 ZGC:当需要极低延迟时
故障排查
常见问题
停顿时间超过目标:
原因:目标设置过于激进或堆内存不足 解决: - 调整停顿时间目标 - 增加堆内存 - 优化应用代码
Mixed GC 过于频繁:
原因:老年代增长过快或阈值设置不当 解决: - 调整并发标记阈值 - 增加新生代大小 - 优化对象生命周期
Full GC 频繁发生:
原因:堆内存不足或参数配置不当 解决: - 增加堆内存 - 调整 G1 参数 - 检查内存泄漏
诊断工具
bash
# 分析 GC 日志
-XX:+PrintGCDetails \
-XX:+PrintGCCause \
-XX:+PrintStringDeduplicationStatistics
# 监控内存使用
jstat -gc <pid> 1s
# 分析堆转储
jmap -dump:format=b,file=heap.hprof <pid>
# 查看 G1 内部状态
jcmd <pid> GC.run_finalization
发展历程与未来
历史发展
- JDK 7u4:首次引入 G1 GC(实验性)
- JDK 8:G1 GC 稳定版本
- JDK 9:G1 GC 成为默认垃圾回收器
- JDK 10+:持续优化和改进
未来发展
- 性能优化:继续优化停顿时间和吞吐量
- 内存效率:减少内存开销
- 易用性:简化配置和调优
- 新特性:支持更多高级特性
总结
G1 GC 是一个设计优秀的现代垃圾回收器,它成功地平衡了停顿时间和吞吐量,特别适合大内存、延迟敏感的服务端应用。通过区域化的内存管理和增量回收策略,G1 GC 提供了可预测的停顿时间,同时保持了较高的吞吐量。虽然它有一定的内存开销和复杂性,但对于现代应用来说,这些代价是值得的。随着 JDK 的不断发展,G1 GC 将继续优化,为 Java 应用提供更好的垃圾回收体验。