ZGC源码解析(三)

2023-03-30  本文已影响0人  allanYan

阶段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中的数据,执行入列动作;

上一篇下一篇

猜你喜欢

热点阅读