美文网首页
G1垃圾回收 - 3

G1垃圾回收 - 3

作者: 程序员札记 | 来源:发表于2023-02-05 08:49 被阅读0次

我们都知道当新生代剩下的空间不够分配会触发GC垃圾回收,新生代的GC是对部分内存进行垃圾回收,GC时间比较少,分区化的G1堆针对新生代的收集的内存也是不固定的。首先我们明白在进行YGC的时候会进行STW。然后会选择需要收集的CSet,针对新生代而言就是整个新生代分区。然后加入收集任务中,去并行处理引用。引用关系搜索完毕之后,就是进行对象引用回收,处理对象晋升,晋升失败的还原对象头,尝试扩展内存等。G1-YGC工作流程如下

do_collection_pause_at_safepoint

直接进入CollectedHeap.cpp#evacuate_collection_set方法一探其究。下图为并行清理CSet方法的工作流程

[图片上传中...(image-9d4305-1675644518441-3)]

  1. 使用G1RootProcessor类去执行根扫描,扫描直接强引用。主要是JVM根和Java根。使用G1ParCopyHelper把对象复制。

    • Java根

      • 类加载器

        深度遍历当前类的加载的所有存活的Klass对象,找到之后复制到Survivor区或者晋升老年代。

      • 线程栈

        处理Java线程栈和本地方法栈中找,通过StackFrameStream的next执行飞到Sender,从而得到调用者,进而其找到关联的活跃堆内对象,将其复制到Survivor区或者晋升老年代。

      知道了G1RootProcessor类会从上述的两个大方向上去找活跃对象,那么直接看代码,g1RootProcessor.cpp#evacuate_roots

      • void G1RootProcessor::process_java_roots(OopClosure* strong_roots,
        CLDClosure* thread_stack_clds,
        CLDClosure* strong_clds,
        CLDClosure* weak_clds,
        CodeBlobClosure* strong_code,
        G1GCPhaseTimes* phase_times,
        uint worker_i) {
        assert(thread_stack_clds == NULL || weak_clds == NULL, "There is overlap between those, only one may be set");
        // Iterating over the CLDG and the Threads are done early to allow us to
        // first process the strong CLDs and nmethods and then, after a barrier,
        // let the thread process the weak CLDs and nmethods.
        {
        G1GCParPhaseTimesTracker x(phase_times, G1GCPhaseTimes::CLDGRoots, worker_i);
        if (!_process_strong_tasks->is_task_claimed(G1RP_PS_ClassLoaderDataGraph_oops_do)) {
        ClassLoaderDataGraph::roots_cld_do(strong_clds, weak_clds);
        }
        }
        
        {
        G1GCParPhaseTimesTracker x(phase_times, G1GCPhaseTimes::ThreadRoots, worker_i);
        Threads::possibly_parallel_oops_do(strong_roots, thread_stack_clds, strong_code);
        }
        }
        
        void ClassLoaderDataGraph::roots_cld_do(CLDClosure* strong, CLDClosure* weak) {
        for (ClassLoaderData* cld = _head; cld != NULL; cld = cld->_next) {
        CLDClosure* closure = cld->keep_alive() ? strong : weak;
        if (closure != NULL) {
        closure->do_cld(cld);
        }
        }
        }
        
        void ClassLoaderData::oops_do(OopClosure* f, KlassClosure* klass_closure, bool must_claim) {
        if (must_claim && !claim()) {
        return;
        }
        
        f->do_oop(&_class_loader);
        _dependencies.oops_do(f);
        _handles->oops_do(f);
        if (klass_closure != NULL) {
        classes_do(klass_closure);
        }
        }
        void ClassLoaderData::classes_do(KlassClosure* klass_closure) {
        for (Klass* k = _klasses; k != NULL; k = k->next_link()) {
        klass_closure->do_klass(k);
        assert(k != k->next_link(), "no loops!");
        }
        }
        

      最终发现调用的G1KlassScanClosure中的do_klass

      • class G1KlassScanClosure : public KlassClosure {
        G1ParCopyHelper* _closure;
        bool _process_only_dirty;
        int _count;
        public:
        G1KlassScanClosure(G1ParCopyHelper* closure, bool process_only_dirty)
        : _process_only_dirty(process_only_dirty), _closure(closure), _count(0) {}
        void do_klass(Klass* klass) {
        if (!_process_only_dirty || klass->has_modified_oops()) {
        klass->clear_modified_oops();
        _closure->set_scanned_klass(klass);
        klass->oops_do(_closure);
        _closure->set_scanned_klass(NULL);
        }
        _count++;
        }
        };
        

      主要执行klass->oops_do(_closure);,这个f为G1ParCopyHelper的对象,所以最终调用的g1CollectedHeap.cpp@G1ParCopyClosure#do_oop_workG1ParCopyHelperdo_oop最终调用do_oop_work来把活跃对象复制到新分区。

      针对线程的处理则是在thread.cpp#possibly_parallel_oops_doThreads::possibly_parallel_oops_do(strong_roots, thread_stack_clds, strong_code);实际调用JavaThread::oops_do遍历栈桢

      • void Thread::oops_do(OopClosure* f, CLDClosure* cld_f, CodeBlobClosure* cf) {
        active_handles()->oops_do(f);
        // Do oop for ThreadShadow
        f->do_oop((oop*)&_pending_exception);
        handle_area()->oops_do(f);
        }
        void JavaThread::oops_do(OopClosure* f, CLDClosure* cld_f, CodeBlobClosure* cf) {
        Thread::oops_do(f, cld_f, cf);
        assert( (!has_last_Java_frame() && java_call_counter() == 0) ||
        (has_last_Java_frame() && java_call_counter() > 0), "wrong java_sp info!");
        
        if (has_last_Java_frame()) {
        RememberProcessedThread rpt(this);
        if (_privileged_stack_top != NULL) {
        _privileged_stack_top->oops_do(f);
        }
        if (_array_for_gc != NULL) {
        for (int index = 0; index < _array_for_gc->length(); index++) {
        f->do_oop(_array_for_gc->adr_at(index));
        }
        }
        for (MonitorChunk* chunk = monitor_chunks(); chunk != NULL; chunk = chunk->next()) {
        chunk->oops_do(f);
        }
        for(StackFrameStream fst(this); !fst.is_done(); fst.next()) {
        fst.current()->oops_do(f, cld_f, cf, fst.register_map());
        }
        }
        set_callee_target(NULL);
        assert(vframe_array_head() == NULL, "deopt in progress at a safepoint!");
        GrowableArray* list = deferred_locals();
        if (list != NULL) {
        for (int i = 0; i < list->length(); i++) {
        list->at(i)->oops_do(f);
        }
        }
        f->do_oop((oop*) &_threadObj);
        f->do_oop((oop*) &_vm_result);
        f->do_oop((oop*) &_exception_oop);
        f->do_oop((oop*) &_pending_async_exception);
        
        if (jvmti_thread_state() != NULL) {
        jvmti_thread_state()->oops_do(f);
        }
        }
        

      从JNI本地代码栈和JVM内部方法栈中找活跃对象,从java栈中找,遍历Monitor块,遍历jvmti(JVM Tool Interface)这里主要使用是JavaAgent。最后执行G1ParCopyHelperdo_oop最终调用do_oop_work来把活跃对象复制到新分区。

    • JVM根

      一些全局JVM对象,如Universe,JNIHandles,SystemDictionary,StringTable等等

      void G1RootProcessor::process_vm_roots(OopClosure* strong_roots,
                                             OopClosure* weak_roots,
                                             G1GCPhaseTimes* phase_times,
                                             uint worker_i) {
      {
          G1GCParPhaseTimesTracker x(phase_times, G1GCPhaseTimes::UniverseRoots, worker_i);
          if (!_process_strong_tasks->is_task_claimed(G1RP_PS_Universe_oops_do)) {
            Universe::oops_do(strong_roots);
          }
        }
       ....
       void Universe::oops_do(OopClosure* f, bool do_all) {
      
        f->do_oop((oop*) &_int_mirror);
        f->do_oop((oop*) &_float_mirror);
        f->do_oop((oop*) &_double_mirror);
       ........
      }
      

      针对JVM根 同样也是调用的G1ParCopyHelperdo_oop只不过对JVM根而言则是各种全局对象。例如Univers

    g1CollectedHeap.cpp@G1ParCopyClosure#do_oop_work工作流程如下
    [图片上传中...(image-ee39c-1675644518439-2)]

    执行对象复制复制的操作在G1ParScanThreadState#copy_to_survivor_space方法中。具体处理如下
    [图片上传中...(image-eb176-1675644518439-1)]

  2. 处理RSet

  • 我们在G1ParTask的work方法中来看处理RSet的入口。

    • void G1RootProcessor::scan_remembered_sets(G1ParPushHeapRSClosure* scan_rs,
      OopClosure* scan_non_heap_weak_roots,
      uint worker_i) {
      ...
      _g1h->g1_rem_set()->oops_into_collection_set_do(scan_rs, &scavenge_cs_nmethods, worker_i);
      }
      

    主要是去执行G1RemSet中的oops_into_collection_set_do方法。主要信息更新RSet和扫描RSet。

    • void G1RemSet::oops_into_collection_set_do(G1ParPushHeapRSClosure* oc,
      CodeBlobClosure* code_root_cl,
      uint worker_i) {
      DirtyCardQueue into_cset_dcq(&_g1->into_cset_dirty_card_queue_set());
      updateRS(&into_cset_dcq, worker_i);
      scanRS(oc, code_root_cl, worker_i);
      _cset_rs_update_cl[worker_i] = NULL;
      }
      

    这里看到有个DCQ,在研究RSet的时候就遇到这种队列,当时说的是给予Mutator用于记录应用线程运行时引用情况,这里这个主要是用于记录复制失败后,要保留的引用,此队列数据将传递到用于管理RSet更新的DirtyCardQueueSet。

    • 更新RSet
    > 主要用于把上面这个DCQ对象存到RSet的PRT当中。
    > 
    > *   ```
    >     G1GCParPhaseTimesTracker x(_g1p->phase_times(), G1GCPhaseTimes::UpdateRS, worker_i);
    >     // Apply the given closure to all remaining log entries.
    >     RefineRecordRefsIntoCSCardTableEntryClosure into_cset_update_rs_cl(_g1, into_cset_dcq);
    >     
    >     _g1->iterate_dirty_card_closure(&into_cset_update_rs_cl, into_cset_dcq, false, worker_i);
    >     }
    >     void G1CollectedHeap::iterate_dirty_card_closure(CardTableEntryClosure* cl,
    >     DirtyCardQueue* into_cset_dcq,
    >     bool concurrent,
    >     uint worker_i) {
    >     // Clean cards in the hot card cache
    >     G1HotCardCache* hot_card_cache = _cg1r->hot_card_cache();
    >     hot_card_cache->drain(worker_i, g1_rem_set(), into_cset_dcq);
    >     
    >     DirtyCardQueueSet& dcqs = JavaThread::dirty_card_queue_set();
    >     size_t n_completed_buffers = 0;
    >     while (dcqs.apply_closure_to_completed_buffer(cl, worker_i, 0, true)) {
    >     n_completed_buffers++;
    >     }
    >     g1_policy()->phase_times()->record_thread_work_item(G1GCPhaseTimes::UpdateRS, worker_i, n_completed_buffers);
    >     dcqs.clear_n_completed_buffers();
    >     assert(!dcqs.completed_buffers_exist_dirty(), "Completed buffers exist!");
    >     }
    >     ```
    >     
    >     
    > 
    > 首先使用`RefineRecordRefsIntoCSCardTableEntryClosure`闭包处理,**处理整个卡中如果存在对堆内对象的引用,就是脏卡,就需要入队,被Refine线程处理**。
    > 
    > `iterate_dirty_card_closure`方法处理DCQS中剩余的DCQ,和Java线程处理方式一样。
    
    • 扫描Rset
    > 根据Rset中的信息找到引用者
    > 
    > *   ```
    >     void G1RemSet::scanRS(G1ParPushHeapRSClosure* oc,
    >     CodeBlobClosure* code_root_cl,
    >     uint worker_i) {
    >     double rs_time_start = os::elapsedTime();
    >     HeapRegion *startRegion = _g1->start_cset_region_for_worker(worker_i);
    >     
    >     ScanRSClosure scanRScl(oc, code_root_cl, worker_i);
    >     
    >     _g1->collection_set_iterate_from(startRegion, &scanRScl);
    >     scanRScl.set_try_claimed();
    >     _g1->collection_set_iterate_from(startRegion, &scanRScl);
    >     
    >     double scan_rs_time_sec = (os::elapsedTime() - rs_time_start)
    >     - scanRScl.strong_code_root_scan_time_sec();
    >     
    >     assert(_cards_scanned != NULL, "invariant");
    >     _cards_scanned[worker_i] = scanRScl.cards_done();
    >     
    >     _g1p->phase_times()->record_time_secs(G1GCPhaseTimes::ScanRS, worker_i, scan_rs_time_sec);
    >     _g1p->phase_times()->record_time_secs(G1GCPhaseTimes::CodeRoots, worker_i, scanRScl.strong_code_root_scan_time_sec());
    >     }
    >     ```
    >     
    >     
    > 
    > 使用GC线程id分片处理不同的分区,执行流程主要是俩次扫描分区。处理一般对象和代码对象主要处理内联优化之后的代码引用对象。主要执行流程如下
    > [图片上传中...(image-63353b-1675644518437-0)]
    
  1. 对象复制
  • 主要处理根扫描出的对象和 RSet中找到的子对象全部复制到新的分区当中。所有的对象都被放在ParScanState的队列中。执行复制的过程就是从该队列中出队,处理不同的对象类型。最终调用deal_with_reference方法来处理。把cset中所有的活跃对象都复制到新的分区的Survivor或者老年代当中。

相关文章

  • Java 垃圾回收器之G1详解

    Java 垃圾回收器之G1详解 概述 G1垃圾回收器是在Java7 update 4之后引入的一个新的垃圾回收器。...

  • 24-一步一图带你理清G1垃圾回收流程

    G1垃圾回收流程 G1的垃圾回收流程主要是从新生代回收开始,新生代回收与并发标记再到混合回收,接下来我们就先来说第...

  • jvm 优化篇-(7)-G1回收过程(-XX:MaxGCPaus

    1、G1垃圾回收♻️过程 1.1、触发混合回收♻️条件: -XX:InitiatingHeapOccupancy...

  • G1垃圾回收器在并发场景调优

    一、序言 目前企业级主流使用的Java版本是8,垃圾回收器支持手动修改为G1,G1垃圾回收器是Java 11的默认...

  • Mac下IDEA卡顿问题解决

    修改前: 修改后: 主要参数及说明 这里我保留了G1的垃圾回收方式。 -XX:+UseG1GC 据说G1垃圾回收是...

  • G1垃圾回收器

    垃圾回收器的发展历程 背景 01、G1解决的问题 G1垃圾回收器是04年正式提出,12开始正式支持,在17年作为J...

  • JVM之G1垃圾回收器

    G1垃圾回收器 stop the world,这个是最痛的一个点!无论是新生代垃圾回收,还是老年代垃圾回收,都会或...

  • G1垃圾回收 - 3

    我们都知道当新生代剩下的空间不够分配会触发GC垃圾回收,新生代的GC是对部分内存进行垃圾回收,GC时间比较少,分区...

  • JVM Java G1 垃圾收集器

    本文简单介绍了垃圾收集的几种常见式,重点说明了G1回收的原理(毕竟JDK1.9 G1会是默认的GC回收器–-...

  • 垃圾回收算法

    G1垃圾回收和其他的区别 串行回收:主要面向单线程环境 并行/吞吐量回收器:JVM默认回收器,Parallel c...

网友评论

      本文标题:G1垃圾回收 - 3

      本文链接:https://www.haomeiwen.com/subject/pypahdtx.html