G1源码之fullGC算法详解

一.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源码之fullGC算法详解

如上图在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_HEAP, mtInternal) GrowableArray<
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值