G1垃圾收集器源码级深度解析:CSet、RSet与混合回收机制
G1垃圾收集器作为JDK9+的默认GC,其分区化设计和可预测停顿时间特性使其成为大内存场景的首选。本文将从源码层面深入剖析G1的Collection Set(CSet)、Remembered Set(RSet)、并发优化线程以及Young GC、Mixed GC、Full GC三种回收模式的完整流程,助你彻底掌握G1的核心机制。
📋 文章目录
- 一、G1核心数据结构解析
- 二、G1的线程模型
- 三、Young GC:年轻代回收详解
- 四、Mixed GC:混合式回收全流程
- 五、Full GC:全局垃圾回收
- 六、G1关键参数配置
- 七、总结与最佳实践
一、G1核心数据结构解析
1.1 CSet(Collection Set 回收集合)
CSet是G1垃圾收集器的核心概念之一,代表每次GC暂停时回收的一系列目标分区。
CSet的特点:
- 在任意一次收集暂停中,CSet所有分区都会被释放
- 内部存活的对象都会被转移到分配的空闲分区中
- 无论是Young GC还是Mixed GC,工作机制都是一致的
CSet的两种类型:
| 类型 | 说明 |
|---|---|
| CSet of Young Collection | 只专注回收Young Region和Survivor Region |
| CSet of Mixed Collection | 通过RSet计算Region中对象的活跃度,筛选回收收益最高的老年代分区 |
Mixed GC的准入阈值:
# 活跃度阈值(默认85%),只有活跃度高于此值的分区才准入CSet-XX:G1MixedGCLiveThresholdPercent=85# CSet与整个堆的比例上限(默认10%)-XX:G1OldCSetRegionThresholdPercent=10这意味着在混合回收中,G1会优先选择垃圾最多(回收收益最高)的老年代分区加入CSet,而非所有老年代分区。
二、G1的线程模型
2.1 App Thread(用户线程)
App Thread就是执行Java程序业务逻辑的实际线程,运行用户代码。
2.2 Concurrence Refinement Thread(并发优化线程)
这是G1中非常重要的一个后台线程,主要用来处理代间引用关系。
工作原理
- 跨区引用检测:当赋值语句发生后,G1通过**写屏障(Write Barrier)**技术,筛选出此次赋值是否是跨Region之间的引用
- 日志缓冲:如果是跨区引用,在线程的内存缓冲区写一条log
- 缓冲区切换:一旦缓冲区写满,就重新起一块缓冲继续写,原有缓冲区进入全局缓冲区
- RSet更新:Concurrence Refinement Thread扫描全局缓冲区的日志,更新各个Region的RSet
关键参数
# 并发优化线程数(默认等于ParallelGCThreads)-XX:G1ConcRefinementThreads# 绿色阈值(正常状态)-XX:G1ConcRefinementGreenZone# 黄色阈值(警告状态,增加线程)-XX:G1ConcRefinementYellowZone# 红色阈值(危险状态,可能阻塞App Thread)-XX:G1ConcRefinementRedZone注意:如果全局缓冲区日志积累过多,G1会调用更多线程处理,甚至会阻塞App Thread来处理,造成应用任务堵塞,必须避免这种现象。
三、Young GC:年轻代回收详解
3.1 Young GC触发条件
Eden区大小范围:
[ -XX:G1NewSizePercent, -XX:G1MaxNewSizePercent ] = [ 整堆5%, 整堆60% ]触发逻辑:
- G1会计算当前Eden区回收大概需要多久时间
- 如果回收时间远小于
-XX:MaxGCPauseMillis(默认200ms),则增加年轻代的Region继续存放新对象,不会马上触发Young GC - 当G1计算的回收时间接近目标停顿时间时,触发Young GC
3.2 Young GC执行步骤
Young GC的并行任务包括:根扫描、更新RSet、对象复制。
第一步:根扫描(Root Scanning)
主要逻辑在g1RootProcessor.cpp的evacuate_roots方法中:
voidG1RootProcessor::evacuate_roots(...){// 1. 处理Java根process_java_roots(closures,phase_times,worker_i);// 2. 处理JVM根process_vm_roots(closures,phase_times,worker_i);// 3. 处理String Table根process_string_table_roots(closures,phase_times,worker_i);}处理Java根(process_java_roots):
- 处理所有已加载类的元数据
- 处理所有Java线程当前栈帧的引用和虚拟机内部线程
处理JVM根(process_vm_roots):
- 处理JVM内部使用的引用(Universe和SystemDictionary)
- 处理JNI句柄
- 处理对象锁的引用
- 处理java.lang.management管理和监控相关类的引用
- 处理JVMTI(JVM Tool Interface)的引用
- 处理AOT静态编译的引用
第二步:对象复制
对象复制的核心逻辑在do_oop_work方法中:
voidG1ParCopyClosure<barrier,do_mark_object>::do_oop_work(T*p){// 1. 判断对象是否在CSet中constInCSetState state=_g1->in_cset_state(obj);if(state.is_in_cset()){// 2. 判断对象是否已经copy过markOop m=obj->mark();if(m->is_marked()){// 已经copy过,直接找到新对象forwardee=(oop)m->decode_pointer();}else{// 没有copy过,调用copy_to_survivor_spaceforwardee=_par_scan_state->copy_to_survivor_space(state,obj,m);}// 3. 修改老对象的对象头,指向新对象地址,并将锁标志位置为11oopDesc::encode_store_heap_oop(p,forwardee)