一.full gc触发的时机
国际惯例,我们先从full gc的触发时机开始看起,读过笔者之前博客的朋友应该都熟悉这个套路,学习源码都需要先找到入口,而gc算法的入口其实就是其触发的时机。
关于full gc触发的时机其实是 内存申请失败,晋升失败,疏散失败,元空间gc,调用system.gc时会触发full gc ,相关调用关系如下:
//(大对象或内存)申请失败
//年轻代对象晋升老年代的疏散(即回收)失败
//其他原因的疏散(即回收)失败
G1CollectedHeap::mem_allocate
--VM_G1CollectForAllocation::doit()
----G1CollectedHeap::satisfy_failed_allocation()
------G1CollectedHeap::do_collection()
//入参是GC触发原因:
//other = system.gc
//collect_as_vm_thread = metadata_gc
G1CollectedHeap::collect(GCCause::Cause cause)
--VM_G1CollectFull::doit()
----G1CollectedHeap::do_full_collection()
------ G1CollectedHeap::do_collection()
//看过笔者之前的文章的朋友可以看到VM_G1CollectForAllocation和VM_G1CollectFull是gc时候的任务类
//方法调用到这层时已经进入stw了,有兴趣的朋友可以翻一翻笔者往期博客,有关于stw的实现讲解,这里就先不论述
我们可以看到,最终调用的都是
G1CollectedHeap::do_collection()这个方法去执行full gc的逻辑,下面我们来逐步拆解full gc的过程。
二.full gc前准备
G1CollectedHeap::do_collection()从方法名我们就可以看出这是关于gc的方法,但是这个方法再gc前还会做一些准备工作,我们直接来看下:
//参数分别是:
//1.是否是明确的gc即为false则表示gc应最少满足回收word_size的大小,为true则表示system.gc或整个堆回收
//2.是否清除所有软引用
//3.要申请的空间大小
bool G1CollectedHeap::do_collection(bool explicit_gc,
bool clear_all_soft_refs,
size_t word_size) {
//检查是否正在执行
if (GC_locker::check_active_before_gc()) {
return false;
}
//声明一些gc计时器
STWGCTimer* gc_timer = G1MarkSweep::gc_timer();
gc_timer->register_gc_start();
SerialOldTracer* gc_tracer = G1MarkSweep::gc_tracer();
gc_tracer->report_gc_start(gc_cause(), gc_timer->gc_start());
SvcGCMarker sgcm(SvcGCMarker::FULL);
ResourceMark rm;
//gc前打印相关日志
print_heap_before_gc();
trace_heap_before_gc(gc_tracer);
//元空间之前的使用量
size_t metadata_prev_used = MetaspaceAux::allocated_used_bytes();
HRSPhaseSetter x(HRSPhaseFullGC);
verify_region_sets_optional();
const bool do_clear_all_soft_refs = clear_all_soft_refs ||
collector_policy()->should_clear_all_soft_refs();
//声明清理所有软引用闭包
ClearedAllSoftRefs casr(do_clear_all_soft_refs, collector_policy());
{
IsGCActiveMark x;
// 一些时间计算
assert(gc_cause() != GCCause::_java_lang_system_gc || explicit_gc, "invariant");
gclog_or_tty->date_stamp(G1Log::fine() && PrintGCDateStamps);
//追踪cpu时间
TraceCPUTime tcpu(G1Log::finer(), true, gclog_or_tty);
{
//gc时间追踪器
GCTraceTime t(GCCauseString("Full GC", gc_cause()), G1Log::fine(), true, NULL);
TraceCollectorStats tcs(g1mm()->full_collection_counters());
TraceMemoryManagerStats tms(true /* fullGC */, gc_cause());
double start = os::elapsedTime();
//记录full gc开始
g1_policy()->record_full_collection_start();
//这里是为了以后更换新的日志框架时留的口子
wait_while_free_regions_coming();
//这里是终止并发标记的根区域扫描
_cm->root_regions()->abort();
_cm->root_regions()->wait_until_scan_finished();
//将second_free_list(不为空的话)加入到free_list中
append_secondary_free_list_if_not_empty_with_lock();
//这个方法是gc前的准备工作,主要是用虚拟数组填充tlab中,方便退回tlab
gc_prologue(true);
//增加统计
increment_total_collections(true /* full gc */);
increment_old_marking_cycles_started();
//gc前验证
verify_before_gc();
//在full gc前生成一些dump信息
pre_full_gc_dump(gc_timer);
//禁用并发标记引用发现器(用于并发处理非强引用)并清空相关发现列表
ref_processor_cm()->disable_discovery();
ref_processor_cm()->abandon_partial_discovery();
ref_processor_cm()->verify_no_references_recorded();
//终止并发标记
concurrent_mark()->abort();
//释放young region正在使用的region
release_mutator_alloc_region();
//取消正在使用的老年代region
abandon_gc_alloc_regions();
//清理记忆集合
g1_rem_set()->cleanupHRRS();
_hr_printer.start_gc(true /* full */, (size_t) total_collections());
//清除回收集合,和incremental回收集合,再gc结束后重建
abandon_collection_set(g1_policy()->inc_cset_head());
g1_policy()->clear_incremental_cset();
g1_policy()->stop_incremental_cset_building();
//清空region sets 包括old_sets, young_list和free_list
tear_down_region_sets(false /* free_list_only */);
//修改gcs_are_young标记
g1_policy()->set_gcs_are_young(true);
ReferenceProcessorMTDiscoveryMutator stw_rp_disc_ser(ref_processor_stw(), false);
ReferenceProcessorIsAliveMutator stw_rp_is_alive_null(ref_processor_stw(), NULL);
//开启stw引用发现器,用于处理非强引用
ref_processor_stw()->enable_discovery(true /*verify_disabled*/, true /*verify_no_refs*/);
ref_processor_stw()->setup_policy(do_clear_all_soft_refs);
// 开始回收工作
{
HandleMark hm;
G1MarkSweep::invoke_at_safepoint(ref_processor_stw(), do_clear_all_soft_refs);
}
//gc 结束收尾工作,先忽略一会我们在分析
.....
}
我们看到在真正的gc开始之前是做了很多准备工作的,包括创建gc时间统计器,开启stw引用发现器,关闭cm引用发现器,填充并退回tlab,终止并发标记,取消正在使用的old region和年轻代region,清理记忆集合,清理回收集合等等,这里笔者就不一一展开分析,其主要逻辑也比较简单,有兴趣的读者可以自行查找阅读。而真正包含full gc算法逻辑的方法是:
G1MarkSweep::invoke_at_safepoint() .
三.full gc算法详解
再看full gc主要算法源码之前,我们要明确一点,full gc使用的是 标记压缩清除算法 ,关于这点有很多同学只了解这个概念,并不清除具体是如何实现的,比如压缩后指针怎么调整,如何进行压缩,标记前锁状态怎么处理等等,接下来笔者会将其慢慢拆解分析,我们先看刚刚提到
G1MarkSweep::invoke_at_safepoint()方法:
void G1MarkSweep::invoke_at_safepoint(ReferenceProcessor* rp,
bool clear_all_softrefs) {
SharedHeap* sh = SharedHeap::heap();
//安装软引用处理策略,rp即前面提到的stw引用发现器,用于处理非强引用
GenMarkSweep::_ref_processor = rp;
rp->setup_policy(clear_all_softrefs);
//当gc时,java方法的字节码地址可能会移动,
//我们必须刷新所有的bcp,或者将其转换为bic,防止其gc结束后指向错误的位置
//这里关于bcp和bic(感兴趣的朋友可以自行查看,本文我们不继续深究,先略过):
//当执行 Java 方法时,程序计数器存放 Java 字节码的地址。
//实现上可能有两种形式,一种是相对该方法字节码开始处的偏移量,叫做 bytecode index(简称 bci)。
//另一种是该 Java 字节码指令在内存的地址,叫做 bytecode pointer(简称 bcp)。
CodeCache::gc_prologue();
Threads::gc_prologue();
bool marked_for_unloading = false;
//申请保存markword(对象头中的markword)的栈,用于保存非偏向锁的锁的markword的栈之后会用到
allocate_stacks();
//保存偏向锁的markword
BiasedLocking::preserve_marks();
//第一步:标记标记对象
mark_sweep_phase1(marked_for_unloading, clear_all_softrefs);
//第二步:准备压缩,即计算压缩后的地址
mark_sweep_phase2();
//第三步:调整指针
mark_sweep_phase3();
//第四步:复制对象
mark_sweep_phase4();
//恢复之前_preserved_marks里保存的markword
GenMarkSweep::restore_marks();
//恢复偏向锁
BiasedLocking::restore_marks();
//释放之前申请的用于保存markword的栈
GenMarkSweep::deallocate_stacks();
Threads::gc_epilogue();
CodeCache::gc_epilogue();
JvmtiExport::gc_epilogue();
// 清空引用扫描器
GenMarkSweep::_ref_processor = NULL;
}
就像笔者之前说到的,这里的代码逻辑是比较明显的,相对好阅读一些,在进行gc算法之前调用了
BiasedLocking::preserve_marks() 方法保存偏向锁,笔者在这里解释下为什么要先保存偏向锁:
如上图在g1中对象的锁标记位都在markword(对象头中的markword)中,而gc标记也在markword(对象头中的markword)中,并且两者在相同的位置,所以要想标记对象,我们就需要保存锁标记,等gc结束后再进行恢复。
其实不止是偏向锁,其他锁也会被保存,之后我们就可以看到,我们先看下保存偏向锁的方法:
void BiasedLocking::preserve_marks() {
//判断偏向锁jvm参数
if (!UseBiasedLocking)
return;
//创建保存偏向锁的mark 和oop的栈
//注:和之前非偏向锁的栈不是一个栈,两者是分开保存的
_preserved_mark_stack = new (ResourceObj::C_HEAP, mtInternal) GrowableArray<markOop>(10, true);
_preserved_oop_stack = new (ResourceObj::C_HEA