美文网首页
ZGC源码解析(三)

ZGC源码解析(三)

作者: allanYan | 来源:发表于2023-03-30 15:37 被阅读0次

    阶段3( Pause Mark End)

    接上文,当并发标记完成之后,接下来会试着结束标记阶段,我们知道并发标记阶段标记线程和业务线程是同时在允许着的,就像一边打扫房间,一边仍垃圾;因此需要一个暂停业务线程的阶段,来彻底的完成打扫阶段;

    // Phase 3: Pause Mark End
      while (!pause_mark_end()) {
        // Phase 3.5: Concurrent Mark Continue
        concurrent(mark_continue);
      }
    

    可以看到ZDriver线程会不断尝试结束标记阶段,如果不能结束,则重新标记一次;标记的代码见 concurrent_mark_continue;

    void ZDriver::concurrent_mark_continue() {
      ZStatTimer timer(ZPhaseConcurrentMarkContinue);
      ZHeap::heap()->mark(false /* initial */);
    }
    

    concurrent_mark_continue的执行逻辑和并发标记的代码是一样的,只是不需要GC ROOTS标记,因此initial参数传入的是false;

    接下来我们仔细看看pause_mark_end都做了些什么:

    bool ZMark::try_end() {
      // 将线程上的标记栈数据ZMarkThreadLocalStacks刷新到全局的条带中去
      if (!flush(true /* at_safepoint */)) {
        //如果发现线程的标记栈数据都是空的,意味着没有新的垃圾,标记阶段可以结束
        return true;
      }
    
      return try_complete();
    }
    
    bool ZMark::try_complete() {
      _ntrycomplete++;
    
     //如果条带中还有数据,则再执行一次并发标记,但是只执行200微秒
      ZMarkTask task(this, ZMarkCompleteTimeout);
      _workers->run(&task);
      return _stripes.is_empty();//如果条带为空,则可以结束标记阶段
    }
    

    看到这里,可能大家还是有个疑问,标记阶段结束之后,GC的整个阶段并没有完成,如果在这个阶段,有业务线程重新引用了某个未被标记的对象,那不是会产生问题吗?那么ZGC是怎么解决这类问题的呢?答案就是读屏障;

    inline oop ZBarrierSet::AccessBarrier<decorators, BarrierSetT>::oop_load_in_heap(T* addr) {
      verify_decorators_absent<ON_UNKNOWN_OOP_REF>();
    
      const oop o = Raw::oop_load_in_heap(addr);
      return load_barrier_on_oop_field_preloaded(addr, o);
    }
    
    inline oop ZBarrier::weak_load_barrier_on_oop_field_preloaded(volatile oop* p, oop o) {
      return weak_barrier<is_weak_good_or_null_fast_path, weak_load_barrier_on_oop_slow_path>(p, o);
    }
    
    template <ZBarrierFastPath fast_path, ZBarrierSlowPath slow_path>
    inline oop ZBarrier::weak_barrier(volatile oop* p, oop o) {
      const uintptr_t addr = ZOop::to_address(o);
    
      // Fast path
      if (fast_path(addr)) {
        // Return the good address instead of the weak good address
        // to ensure that the currently active heap view is used.
        return ZOop::from_address(ZAddress::good_or_null(addr));
      }
    
      // Slow path
      const uintptr_t good_addr = slow_path(addr);
    
      if (p != NULL) {
        // The slow path returns a good/marked address or null, but we never mark
        // oops in a weak load barrier so we always heal with the remapped address.
        self_heal<fast_path>(p, addr, ZAddress::remapped_or_null(good_addr));
      }
    
      return ZOop::from_address(good_addr);
    }
    

    简单的概括下,业务线程在访问变量的时候,会标记使用到的对象,这样就不会出现漏标的情况了;

    阶段4(Concurrent Mark Free)

    void ZMark::free() {
      // Free any unused mark stack space
      _allocator.free();
    
      // Update statistics
      ZStatMark::set_at_mark_free(_allocator.size());
    }
    

    这个阶段做的事情比较简单,只是释放标记栈空间同时更新统计数据

    阶段5(Concurrent Process Non-Strong References)

    在阶段2.2 ( Mark)介绍过,并发标记阶段,GC线程会标记各类Reference对象,这些发现的引用(_discovered_list)就是在阶段5进行进一步处理的:

    void ZHeap::process_non_strong_references() {
      // 处理 Soft/Weak/Final/Phantom引用,和业务线程并发执行
      _reference_processor.process_references();
      _weak_roots_processor.process_weak_roots();
      _unload.unlink();
      ZRendezvousClosure cl;
      Handshake::execute(&cl);
      ZResurrection::unblock();
      _unload.purge();
      _reference_processor.enqueue_references();
    }
    
    void ZReferenceProcessor::work() {
      // _discovered_list是个链表,通过Reference对象的discovered字段链接起来
      oop* const list = _discovered_list.addr();
      oop* p = list;
    
      while (*p != NULL) {
        const oop reference = *p;
        const ReferenceType type = reference_type(reference);
    
        if (should_drop(reference, type)) {//如果referent为空或者referent是活跃的,从队列中删除reference
          *p = drop(reference, type);
        } else {
          p = keep(reference, type);//如果referent不活跃了,需要将Reference对象的_referent设置为null
        }
      }
    
      // 将上述处理后的_discovered_list添加到_pending_list
      if (*list != NULL) {
        *p = Atomic::xchg(_pending_list.addr(), *list);
        if (*p == NULL) {
          // First to prepend to list, record tail
          _pending_list_tail = p;
        }
    
        // Clear discovered list
        *list = NULL;
      }
    }
    

    经过上述处理,需要回收的Reference对象都会加入到pending_list链表,并通过Universe::swap_reference_pending_list更新全局变量_reference_pending_list;
    java.lang.ref.Reference中有个ReferenceHandler线程,会取出_reference_pending_list中的数据,执行入列动作;

    相关文章

      网友评论

          本文标题:ZGC源码解析(三)

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