美文网首页
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或者老年代当中。

    相关文章

      网友评论

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

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