G1源码之fullGC算法详解

分类:网校动态日期:2024-08-27 12:26:01人气:


关于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_gcG1CollectedHeap::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() .

G1源码之fullGC算法详解

三.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结束后再进行恢复。

G1源码之fullGC算法详解

其实不止是偏向锁,其他锁也会被保存,之后我们就可以看到,我们先看下保存偏向锁的方法:

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_HEAP, mtInternal) GrowableArray<Handle>(10, true); //遍历所有线程 ResourceMark rm; Thread* cur = Thread::current(); for (JavaThread* thread = Threads::first(); thread != NULL; thread = thread->next()) { //获取最后一个栈帧 if (thread->has_last_Java_frame()) { RegisterMap rm(thread); //遍历栈帧 for (javaVFrame* vf = thread->last_java_vframe(&rm); vf != NULL; vf = vf->java_sender()) { //获取栈帧中的monitors //这里应该是从lock record中获取记录 GrowableArray<MonitorInfo*> *monitors = vf->monitors(); if (monitors != NULL) { int len = monitors->length(); //遍历monitors for (int i = len - 1; i >= 0; i--) { MonitorInfo* mon_info = monitors->at(i); if (mon_info->owner_is_scalar_replaced()) continue; oop owner = mon_info->owner(); if (owner != NULL) { markOop mark = owner->mark(); //如果是偏向锁则保存mark和oop if (mark->has_bias_pattern()) { _preserved_oop_stack->push(Handle(cur, owner)); _preserved_mark_stack->push(mark); } } } } } } }}

在处理偏向锁的保存时,jvm使用的时遍历所有线程栈中栈帧的lock record,如果lock record中记录的是一个偏向锁,则将markword和对象分开保存在栈中。

注:lock record是用于线程栈帧中记录锁信息,这里涉及到synchronized的具体实现,非本文重点,笔者就不过多论述。

之后真正的g1标记压缩清除算法开始了,我们先看 第一步:标记对象 :

G1源码之fullGC算法详解

//标记对——递归遍历所有的对象并标记void G1MarkSweep::mark_sweep_phase1(bool& marked_for_unloading, bool clear_all_softrefs) { GCTraceTime tm("phase 1", G1Log::fine() && Verbose, true, gc_timer()); //打印日志 GenMarkSweep::trace(" 1"); SharedHeap* sh = SharedHeap::heap(); //这里清除遍历ClassLoader的标记,后面遍历的时候还会开启 ClassLoaderDataGraph::clear_claimed_marks(); //熟悉的方法,在youngGC中出现过,扫描强根并标记 //笔者就不展开,我们直接看闭包遍历方法即GenMarkSweep::follow_root_closure的遍历方法 sh->process_strong_roots(true, // activate StrongRootsScope false, // not scavenging. SharedHeap::SO_SystemClasses, &GenMarkSweep::follow_root_closure, &GenMarkSweep::follow_code_root_closure, &GenMarkSweep::follow_klass_closure); //GenMarkSweep::ref_processor()返回的是之前提到的stw引用发现器,用于处理非强引用 ReferenceProcessor* rp = GenMarkSweep::ref_processor(); //装载是否清理软引用的策略 rp->setup_policy(clear_all_softrefs); //发现引用并处理 const ReferenceProcessorStats& stats = rp->process_discovered_references(&GenMarkSweep::is_alive, &GenMarkSweep::keep_alive, &GenMarkSweep::follow_stack_closure, NULL, gc_timer()); gc_tracer()->report_gc_reference_stats(stats); //卸载类和清除系统字典 bool purged_class = SystemDictionary::do_unloading(&GenMarkSweep::is_alive); //卸载native方法 CodeCache::do_unloading(&GenMarkSweep::is_alive, purged_class); //从子类/兄弟类/接口实现类列表中删除死类 Klass::clean_weak_klass_links(&GenMarkSweep::is_alive); //清理没引用的字符串 StringTable::unlink(&GenMarkSweep::is_alive); //清理没引用的符号 SymbolTable::unlink(); //跳过一些验证 .... //在gc之后报告对象数量 gc_tracer()->report_object_count_after_gc(&GenMarkSweep::is_alive);}//GenMarkSweep::follow_root_closure闭包最后调用这个方法遍历处理template <class T> inline void MarkSweep::follow_root(T* p) { T heap_oop = oopDesc::load_heap_oop(p); if (!oopDesc::is_null(heap_oop)) { oop obj = oopDesc::decode_heap_oop_not_null(heap_oop); //如果gc root引用的对象没有被标记则进行标记 if (!obj->mark()->is_marked()) { mark_object(obj); //这个方法会迭代标记对象属性引用的对象 //并将其加入正在标记栈 obj->follow_contents(); } } //这个方法会顺着引用链路遍历下去并标记 //具体实现是不断从标记栈中取出对象取出并遍历其属性引用的对象之后将新对象并加入标记栈,直到栈为空 follow_stack();}//我们看看标记的方法inline void MarkSweep::mark_object(oop obj) { //获取gc前的markword markOop mark = obj->mark(); //创建标记markword即锁标记位是刚刚提到的11的markword并替换对象的markword obj->set_mark(markOopDesc::prototype()->set_marked()); //把一些对象的原markword保存下来 //因为锁的标记位和full gc的标记位是同两位 //如果用于锁就不能标记了,所以保存之后恢复 if (mark->must_be_preserved(obj)) { preserve_mark(obj, mark); }}//看下判断是否保存markword的方法//最后调用这个方法inline bool markOopDesc::must_be_preserved_with_bias(oop obj_containing_mark) const { //偏向锁和对象是分开保存的,偏向锁已经保存过了,所以这里返回false if (has_bias_pattern()) { return false; } //如果klass被锁的对象在gc结束后被恢复时会使用klass的markword,所以这里返回true将其原markword保存 //即防止被锁的class对象干扰到普通对象 markOop prototype_header = prototype_for_object(obj_containing_mark); if (prototype_header->has_bias_pattern()) { return true; } //有锁返回true, 需要保存markword return (!is_unlocked() || !has_no_hash());}

至此,full gc第一步结束了,现在所有的对象已经被标记即markword锁标记位为11,所有的偏向锁和非偏向锁的markword也被保存到相应的栈中。

然后我们看 第二步:准备压缩(计算压缩后的对象地址) :

//现在所有的对象已经被标记,计算新的对象地址void G1MarkSweep::mark_sweep_phase2() { G1CollectedHeap* g1h = G1CollectedHeap::heap(); GCTraceTime tm("phase 2", G1Log::fine() && Verbose, true, gc_timer()); GenMarkSweep::trace("2"); //这里可以理解为找到第一个region,非humongus region //将其当作压缩空间,在之后会用到,这里的压缩空间会记录存活对象压缩后的位置 HeapRegion* r = g1h->region_at(0); CompactibleSpace* sp = r; if (r->isHumongous() && oop(r->bottom())->is_gc_marked()) { sp = r->next_compaction_space(); } //创建用于准备压缩的遍历闭包 G1PrepareCompactClosure blk(sp); g1h->heap_region_iterate(&blk); //这个方法主要是更新大对象region集合,会将回收的大对象region加入代理集合等待之后回收 blk.update_sets();}//我们看看准备压缩的闭包遍历方法bool doHeapRegion(HeapRegion* hr) { //大对象region处理if (hr->isHumongous()) { //大对象region只处理开始的region if (hr->startsHumongous()) {oop obj = oop(hr->bottom()); //如果被标记则将指向自己的指针保存到对象的markword中if (obj->is_gc_marked()) { obj->forward_to(obj);} else { //如果没被标记则释放region free_humongous_region(hr);} } else {assert(hr->continuesHumongous(), "Invalid humongous."); }} else { //一般region处理 //其他的准备压缩,计算存活对象压缩后的指针位置并存到对象的markword中 //这个方法最后会调用准备压缩宏方法,我们一起来看看 //参数是之前传入的压缩空间的封装类 hr->prepare_for_compaction(&_cp); //清理卡表 _mrbs->clear(MemRegion(hr->compaction_top(), hr->end()));}return false;}

我们来看下最后调用的准备压缩宏方法:

//准备压缩region的宏//计算每个对象压缩后的位置#define SCAN_AND_FORWARD(cp,scan_limit,block_is_obj,block_size) { //这个表示当前要压缩到的位置的游标 //先初始化到region底部bottom HeapWord* compact_top; set_compaction_top(bottom()); //初始化压缩空间,这里是g1所以space不为Null跳过 //这里的压缩空间其实是普通的region,和当前遍历处理的region不一定是一个region if (cp->space == NULL) { assert(cp->gen != NULL, "need a generation"); assert(cp->threshold == NULL, "just checking"); assert(cp->gen->first_compaction_space() == this, "just checking"); cp->space = cp->gen->first_compaction_space(); compact_top = cp->space->bottom(); cp->space->set_compaction_top(compact_top); cp->threshold = cp->space->initialize_threshold(); } else { compact_top = cp->space->compaction_top(); } //这里会获取fullGC调用次数来判断是否跳过在存活对象区域中间的死亡区域 //其意义是可以避免频繁压缩,减少性能消耗 uint invocations = MarkSweep::total_invocations(); bool skip_dead = ((invocations % MarkSweepAlwaysCompactCount) != 0); //如果允许跳过死亡区域,则计算可跳过的死亡区域大小 size_t allowed_deadspace = 0; if (skip_dead) { const size_t ratio = allowed_dead_ratio(); allowed_deadspace = (capacity() * ratio / 100) / HeapWordSize; } HeapWord* q = bottom(); //scan_limit()是传入的第二个参数(闭包)实际上返回的是top,即region已经使用的区域大小 HeapWord* t = scan_limit(); //这个代表最后的存活对象的游标,先设置为bottom HeapWord* end_of_live= q; //第一个垃圾对象游标,先设置为end即region尾部 HeapWord* first_dead = end(); LiveRange* liveRange = NULL; _first_dead = first_dead; const intx interval = PrefetchScanIntervalInBytes; //遍历直到top位置,q即当前游标指向的对象 while (q < t) { //判断是否是对象且被标记,即表示存活 if (block_is_obj(q) && oop(q)->is_gc_marked()) { Prefetch::write(q, interval); //获取对象的大小 size_t size = block_size(q); //计算对象压缩后的地址,并将指针存到对象的markword中 //一会我们分析下这个forward()方法 compact_top = cp->space->forward(oop(q), size, cp, compact_top); //移动q游标和最后一个存活对象游标 q += size; end_of_live = q; } else { //不存活的对象处理 HeapWord* end = q; //这个循环是判断后面是不是连续的不存活对象,方便直接跳过 do { Prefetch::write(end, interval); end += block_size(end); } while (end < t && (!block_is_obj(end) || !oop(end)->is_gc_marked())); //根据allowed_deadspace判断是否允许跳过不存活的对象,意义是避免经常压缩 if (allowed_deadspace > 0 && q == compact_top) { size_t sz = pointer_delta(end, q); //填充deadspace,填充成功就把这段当作存活对象 if (insert_deadspace(allowed_deadspace, q, sz)) { compact_top = cp->space->forward(oop(q), sz, cp, compact_top); q = end; end_of_live = end; continue; } } if (liveRange) { liveRange->set_end(q); } //这里有点难以理解,代码运行到这里 //end是这个垃圾区域后的存活对象指针,将存活的对象地址放到垃圾区域开始对象的markword中 //在第三步调整指针的时候可以直接跳过这片垃圾区域 liveRange = (LiveRange*)q; liveRange->set_start(end); liveRange->set_end(end); //修改第一个不存活的对象标记的位置 if (q < first_dead) { first_dead = q; } q = end; } } //遍历结束 if (liveRange != NULL) { liveRange->set_end(q); } _end_of_live = end_of_live; //如果最后一个存活对象位置小于第一个死亡对象的位置,则修改死亡对象的位置到最后存活对象的位置 //若遍历所有使用区域后,无逻辑上的死亡对象,则调整first_dead if (end_of_live < first_dead) { first_dead = end_of_live; } _first_dead = first_dead; //保存压缩位置游标 cp->space->set_compaction_top(compact_top); }

这段代码读起来有点繁琐,主要做了两件事:

1.处理first_dead和end_of_live游标

2.计算存活对象压缩后的指针并保存

第一件事就是笔者画个图来展示下所有region处理完成后两个游标的状态,便于大家直观理解下:

第二件事是在之前提到的 forward() 方法实现的,我们来一起看看:

HeapWord* CompactibleSpace::forward(oop q, size_t size, CompactPoint* cp, HeapWord* compact_top) { //计算当前还可使用的压缩的最大空间,即end - compact_top size_t compaction_max_size = pointer_delta(end(), compact_top); //判断需要的对象大小是否不大于可压缩的最大空间,如果大于则切换下一个region while (size > compaction_max_size) { cp->space->set_compaction_top(compact_top); //获取下一段压缩空间,这里的space->调用的是CompactibleSpace派生类 //HeapRegion重写的方法返回的是下一个非大对象region cp->space = cp->space->next_compaction_space(); // g1 space不为空这里跳过 if (cp->space == NULL) { cp->gen = GenCollectedHeap::heap()->prev_gen(cp->gen); assert(cp->gen != NULL, "compaction must succeed"); cp->space = cp->gen->first_compaction_space(); assert(cp->space != NULL, "generation must have a first compaction space"); } compact_top = cp->space->bottom(); cp->space->set_compaction_top(compact_top); cp->threshold = cp->space->initialize_threshold(); compaction_max_size = pointer_delta(cp->space->end(), compact_top); } //如果当前对象和计算的压缩位置不一样,则将压缩位置的指针保存到对象的markword中 if ((HeapWord*)q != compact_top) { //这个方法是将压缩后的指针存储在对象的markword中 //这里其实是把压缩后的地址封装成markword替换掉原对象的markword //等到第四步进行copy的时候直接取markword的地址即是计算压缩后的地址 q->forward_to(oop(compact_top)); } else { //如果计算后的位置不变,即不需要移动,初始化对象markword,并将其原来位置的指针保存到markword中 q->init_mark(); } //增加压缩位置 compact_top += size; if (compact_top > cp->threshold) cp->threshold = cp->space->cross_threshold(compact_top - size, compact_top); return compact_top;}

到这里所有的对象都已经被处理,存活对象被压缩后的地址被封装成了markword,且已经替换了存活对象原本的markword,等待之后进行copy的时候直接取用即可。

接下来我们来看 第三步:调整指针 :

//调整指针void G1MarkSweep::mark_sweep_phase3() { G1CollectedHeap* g1h = G1CollectedHeap::heap(); GCTraceTime tm("phase 3", G1Log::fine() && Verbose, true, gc_timer()); GenMarkSweep::trace("3"); SharedHeap* sh = SharedHeap::heap(); //这里清除遍历ClassLoader的标记,后面遍历的时候还会开启 ClassLoaderDataGraph::clear_claimed_marks(); //遍历所有根对象,调整指针到新地址,用GenMarkSweep::adjust_pointer_closure闭包遍历调整指针 sh->process_strong_roots(true, // activate StrongRootsScope false, // not scavenging. SharedHeap::SO_AllClasses, &GenMarkSweep::adjust_pointer_closure, NULL, // do not touch code cache here &GenMarkSweep::adjust_klass_closure); //调整弱引用的指针,用GenMarkSweep::adjust_pointer_closure闭包遍历调整指针 g1h->ref_processor_stw()->weak_oops_do(&GenMarkSweep::adjust_pointer_closure); //调整弱引用根的指针,用GenMarkSweep::adjust_pointer_closure闭包遍历调整指针 g1h->g1_process_weak_roots(&GenMarkSweep::adjust_pointer_closure); //调整之前保存的非偏向锁的markword的指针 GenMarkSweep::adjust_marks(); //遍历所有region调整指针的闭包 G1AdjustPointersClosure blk; g1h->heap_region_iterate(&blk);}//这里我们看几个调整指针的方式//1.GenMarkSweep::adjust_pointer_closure闭包//2.GenMarkSweep::adjust_marks()//这两个方式最后会调用这个方法去调整指针template <class T> inline void MarkSweep::adjust_pointer(T* p) { //获取指针指向的对象,即解引用 T heap_oop = oopDesc::load_heap_oop(p); if (!oopDesc::is_null(heap_oop)) { oop obj = oopDesc::decode_heap_oop_not_null(heap_oop); //从对象markword中获取刚刚保存的计算压缩后对象的地址 oop new_obj = oop(obj->mark()->decode_pointer()); if (new_obj != NULL) { //将指针指向新的地址 oopDesc::encode_store_heap_oop_not_null(p, new_obj); } }}//3.G1AdjustPointersClosure的闭包//遍历所有region调整指针的闭包bool doHeapRegion(HeapRegion* r) { //大对象region if (r->isHumongous()) { if (r->startsHumongous()) { oop obj = oop(r->bottom()); obj->adjust_pointers(); } } else { //普通region r->adjust_pointers(); } return false;}

第三种调整指针的方式 G1AdjustPointersClosure闭包这里我们再单独拿出来讲下,最后会调用一个调整指针的方法宏,我们一起来看下:

//调整指针的宏#define SCAN_AND_ADJUST_POINTERS(adjust_obj_size) { HeapWord* q = bottom(); HeapWord* t = _end_of_live; /* Established by "prepare_for_compaction". */ if (q < t && _first_dead > q && !oop(q)->is_gc_marked()) { //因为有些存活的对象初始化markword了,即没有移动位置,所以这里会处理第一个对象没被标记的情况 HeapWord* end = _first_dead; //end为first_dead,如果first_dead大于q则证明第一个对象其实是存活的只不过不需要调整指针 //遍历后面的对象 while (q < end) { //这个方法会获取对象所有属性的引用,并调整引用指向新的地址(原引用对象压缩后的地址) //最后还是会调用MarkSweep::adjust_pointer方法 size_t size = oop(q)->adjust_pointers(); size = adjust_obj_size(size); q += size; } //若first_dead等于t即end_of_live则表示存活对象是连续的 if (_first_dead == t) { q = t; } else { //这里可以理解成读取之前写的LiveRange,跳过连续的垃圾区域 q = (HeapWord*)oop(_first_dead)->mark()->decode_pointer(); } } const intx interval = PrefetchScanIntervalInBytes; while (q < t) { /* prefetch beyond q */ Prefetch::write(q, interval); //存活对象,调整指针 if (oop(q)->is_gc_marked()) { size_t size = oop(q)->adjust_pointers(); size = adjust_obj_size(size); q += size; } else { //这里可以理解成读取之前写的LiveRange,跳过连续的垃圾区域,将q赋值为连续垃圾区域后的存活对象 q = (HeapWord*) oop(q)->mark()->decode_pointer(); } } }

至此,所有的根引用已经指向了新的地址,所有的被标记过存活的对象的属性引用也指向了新的地址,这里的新的地址是指之前引用的对象压缩后的而地址。

然后我们来看下 第四步:复制对象 :

//第四步:复制对象//现在所有的指针都被调整过了,需要移动对象了void G1MarkSweep::mark_sweep_phase4() { G1CollectedHeap* g1h = G1CollectedHeap::heap(); GCTraceTime tm("phase 4", G1Log::fine() && Verbose, true, gc_timer()); GenMarkSweep::trace("4"); //压缩复制对象的闭包 G1SpaceCompactClosure blk; g1h->heap_region_iterate(&blk);}//直接看闭包主要处理方法bool doHeapRegion(HeapRegion* hr) { //大对象region if (hr->isHumongous()) { if (hr->startsHumongous()) { oop obj = oop(hr->bottom()); if (obj->is_gc_marked()) { //如果是存活的则初始化其markword obj->init_mark(); } else { //垃圾对象则不处理,因为在第二步时已经被回收 } hr->reset_during_compaction(); } } else { //普通region,我们看下普通对象如何处理 hr->compact(); } return false;}

普通对象一样是使用了宏方法,我们一起看下:

//压缩空间方法宏//复制所有存活的对象到他们新的空间#define SCAN_AND_COMPACT(obj_size) { HeapWord* q = bottom(); HeapWord* const t = _end_of_live; debug_only(HeapWord* prev_q = NULL); //第一个对象如果没被标记和第三步一样,先处理其第一个对象可能没有移动位置的情况 if (q < t && _first_dead > q && !oop(q)->is_gc_marked()) { //如果first_dead = end_of_live,则设置q到end_of_live //这里代表整齐的region不需要复制和压缩 if (_first_dead == t) { q = t; } else { //否则将q移动到first_dead连续死亡区域之后 q = (HeapWord*) oop(_first_dead)->mark()->decode_pointer(); } } const intx scan_interval = PrefetchScanIntervalInBytes; const intx copy_interval = PrefetchCopyIntervalInBytes; //遍历 while (q < t) { //如果没被标记则跳过连续垃圾区域,类似liveRange if (!oop(q)->is_gc_marked()) { q = (HeapWord*) oop(q)->mark()->decode_pointer(); } else { //否则表示对象存活 Prefetch::read(q, scan_interval); size_t size = obj_size(q); //获取需要copy到目的地的地址 HeapWord* compaction_top = (HeapWord*)oop(q)->forwardee(); Prefetch::write(compaction_top, copy_interval); //复制对象并重置markword //将对象直接复制到之前计算的位置,覆盖垃圾区域 Copy::aligned_conjoint_words(q, compaction_top, size); oop(compaction_top)->init_mark(); q += size; } } //记录压缩之前是否是空的 bool was_empty = used_region().is_empty(); //压缩后重置region相关属性包括top和存活字节数等等 reset_after_compaction(); //压缩处理后如果是空的且压缩前不是空的则清理 if (used_region().is_empty()) { if (!was_empty) clear(SpaceDecorator::Mangle); } else { if (ZapUnusedHeapArea) mangle_unused_area(); } }

现在所有的存活对象已经整齐的排列在新的region中,之前region的相关属性也进行了重置,整个full gc的标记压缩清除阶段结束了,我们回顾一下主要的流程:

四.full gc收尾

整个full gc过程差不多结束了,当然还有一些收尾工作,我们一起来看下:

//一切的开始do_collection方法bool G1CollectedHeap::do_collection(bool explicit_gc, bool clear_all_soft_refs, size_t word_size) { //full gc前期工作,前面已经讲述过了 ..... //full gc { HandleMark hm; // Discard invalid handles created during gc G1MarkSweep::invoke_at_safepoint(ref_processor_stw(), do_clear_all_soft_refs); } //我们看看收尾工作 //重建region集合,并将空的region加入free_list rebuild_region_sets(false /* free_list_only */); //将一些发现列表中没被删除的被发现引用入列 ref_processor_stw()->enqueue_discovered_references(); //跟踪内存使用 MemoryService::track_memory_usage(); //验证stw引用发现器没有引用记录 ref_processor_stw()->verify_no_references_recorded(); //删除已卸载类加载器的元空间,并清理loader_data图 ClassLoaderDataGraph::purge(); MetaspaceAux::verify_metrics(); //验证cm引用发现器没有引用记录 ref_processor_cm()->verify_no_references_recorded(); reset_gc_time_stamp(); //因为有些对象已经移动,所以我们要清除所有记忆集合和card,稍后我们将重建记忆集合 clear_rsets_post_compaction(); check_gc_time_stamps(); //如果有必要的话调整堆大小 resize_if_necessary_after_full_collection(explicit_gc ? 0 : word_size); ...... //重置热卡缓存 G1HotCardCache* hot_card_cache = _cg1r->hot_card_cache(); if (hot_card_cache->use_cache()) { hot_card_cache->reset_card_counts(); hot_card_cache->reset_hot_cache(); } //重建所有记忆集合,根据策略并发执行或单线程执行 if (G1CollectedHeap::use_parallel_gc_threads()) { uint n_workers = AdaptiveSizePolicy::calc_active_workers(workers()->total_workers(), workers()->active_workers(), Threads::number_of_non_daemon_threads()); workers()->set_active_workers(n_workers); set_par_threads(n_workers); ParRebuildRSTask rebuild_rs_task(this); set_par_threads(workers()->active_workers()); workers()->run_task(&rebuild_rs_task); set_par_threads(0); reset_heap_region_claim_values(); } else { RebuildRSOutOfRegionClosure rebuild_rs(this); heap_region_iterate(&rebuild_rs); } //重新注册编译后的java方法代码(因为之前卸载了) rebuild_strong_code_roots(); //计算元空间的新大小 if (true) { // FIXME MetaspaceGC::compute_new_size(); } //抛弃脏卡队列中的信息 JavaThread::dirty_card_queue_set().abandon_logs(); //重置young_list信息 _young_list->reset_sampled_info(); //增加旧标记(指full gc和并发标记)周期记录 increment_old_marking_cycles_completed(false /* concurrent */); //验证hrs 和region set _hrs.verify_optional(); verify_region_sets_optional(); verify_after_gc(); g1_policy()->start_incremental_cset_building(); //清理cset_fast_test clear_cset_fast_test(); //重新初始化年轻代使用区域 init_mutator_alloc_region(); double end = os::elapsedTime(); //记录full gc结束(这个方法会设置开启youngGC标记表示full gc结束后开启youngGC和重置一些参数) g1_policy()->record_full_collection_end(); if (G1Log::fine()) { g1_policy()->print_heap_transition(); } //更新内存大小(包括元空间) g1mm()->update_sizes(); gc_epilogue(true); } //后面是打印一些日志 if (G1Log::finer()) { g1_policy()->print_detailed_heap_transition(true /* full */); } print_heap_after_gc(); trace_heap_after_gc(gc_tracer); post_full_gc_dump(gc_timer); gc_timer->register_gc_end(); gc_tracer->report_gc_end(gc_timer->gc_end(), gc_timer->time_partitions()); } return true;}

至此整个full gc的流程结束了。

用户评论

一个人的荒凉

这篇文章简直是Java内存管理的宝典!作者深入浅出地解释了Full GC算法,让我的理解更加清晰。特别是对于那些在并发编程中遇到内存泄漏问题的开发者来说,这篇文章简直就是救星。

    有20位网友表示赞同!

她最好i

虽然内容很专业,但是读起来并不枯燥。作者巧妙地运用了例子,帮助我理解复杂的概念。对于像我这样对Java内存管理感兴趣的程序员来说,这是一篇不可多得的好文。

    有5位网友表示赞同!

艺菲

全篇内容详尽,从头到尾没有遗漏任何一个细节。对于需要深入研究Full GC算法的开发者来说,这篇博客简直就是福音。强烈推荐给所有Java程序员!

    有8位网友表示赞同!

爱你的小笨蛋

读完这篇关于Full GC算法的文章,我感觉自己像是在黑暗中找到了光明。作者的解释清晰明了,让人一读就懂。对于那些正在为GC问题困扰的开发者,这篇博客绝对是必读。

    有12位网友表示赞同!

暖瞳

这篇文章的内容丰富,涵盖了Full GC算法的所有关键点。但我觉得,对于初学者来说,可能需要一些时间来消化。不过,对于有经验的开发者来说,它提供了非常有价值的见解。

    有12位网友表示赞同!

颓废人士

作者的写作技巧非常好,能够将复杂的概念以简单易懂的方式呈现出来。我特别喜欢他如何通过实例来解释Full GC算法,这让整个学习过程变得非常有趣。

    有11位网友表示赞同!

米兰

读完这篇关于G1源码的博客后,我对Full GC算法有了全新的认识。作者不仅详细解释了算法的工作原理,还提供了一些实用的建议,帮助我们更有效地使用Java内存。

    有7位网友表示赞同!

箜篌引

这篇文章对我来说是一个巨大的收获。我以前一直对Full GC算法感到困惑,但现在终于明白了它的运作方式。感谢作者分享这么有价值的信息!

    有10位网友表示赞同!

ゞ香草可樂ゞ草莓布丁

对于那些想要深入理解Java内存管理机制的人来说,这篇博客是必看的。作者不仅讲解了Full GC算法,还分享了一些实用的调试技巧,真是棒极了!

    有19位网友表示赞同!

铁树不曾开花

虽然文章内容丰富,信息量大,但对于初学者来说,可能会觉得有些难以消化。不过,随着阅读的深入,你会发现其中的价值所在。

    有10位网友表示赞同!

凉月流沐@

我一直对Java内存管理感兴趣,这篇关于Full GC算法的博客让我大开眼界。作者不仅解释了算法本身,还探讨了它在实际应用中的意义,真是受益匪浅。

    有11位网友表示赞同!

荒野情趣

读完这篇文章,我对Full GC算法的理解提升了一个档次。作者的分析深入浅出,让我能够轻松掌握这个复杂的话题。强烈推荐给所有Java开发者!

    有6位网友表示赞同!

温柔腔

这篇文章对于深入理解Java内存管理至关重要。作者通过丰富的实例和详细的解释,让复杂的Full GC算法变得易于理解。绝对值得花时间阅读。

    有9位网友表示赞同!

眉黛如画

对于那些想要提高Java编程技能的开发者来说,这篇关于Full GC算法的博客是一份宝贵的资源。作者的解释既全面又深入,真正帮助我们掌握了这一重要概念。

    有17位网友表示赞同!

苍白的笑〃

我一直在寻找有关Full GC算法的深入解析,直到发现了这篇博客。作者的解释非常详尽,从基础概念到高级应用,一应俱全。强烈推荐给所有Java开发者!

    有10位网友表示赞同!

呆檬

这篇文章让我对Full GC算法有了全新的认识。作者不仅解释了理论,还提供了实用的代码示例,帮助我更好地理解和应用这一概念。非常感谢作者的分享!

    有13位网友表示赞同!

放血

对于那些想要在Java内存管理领域取得突破的开发者来说,这篇博客是不可或缺的资源。作者的解释既专业又深入,让我对Full GC算法有了更深刻的理解。

    有13位网友表示赞同!

怀念·最初

在这篇文章中,作者成功地将复杂的Full GC算法概念分解成了易于理解的部分。无论是初学者还是经验丰富的开发者,都能从中获益匪浅。强烈推荐!

    有11位网友表示赞同!

本文永久网址:

获取方案
咨询电话
13697281325
TOP 在线咨询
TOP TOP