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 RegionCSet 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 GC3.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方法中:voidG1ParCopyClosurebarrier,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)