gc过程中reference对象的处理

引用对象结构

数据结构定义在referenceProcessor.hpp。定义了以下4种类型的结构。

_discoveredSoftRefs
_discoveredWeakRefs
_discoveredFinalRefs
_discoveredPhantomRefs

每一个结构相对应的数据结构为 DiscoveredList,可以理解为与具体的Reference相同的结构,类似一个处理链表,里面的每一个节点都对应着java中的reference对象。这里仍然采用头指针+length的结构来持有所有需要处理的reference对象。在这里面存放的对象都表示在相应的处理过程中还没有被放入java Reference中pending结构的对象。

从总体上的处理逻辑来看,可以理解为。在整个gc过程中,首先在jvm内部维护一套需要被放到pending中的引用链,然后处理这些引用链,处理完之后将相应的数据重新附到pending中,清除jvm内部数据。这样达到一个reference的处理过程。

对象何时放入DiscoveredList中

在gc的某个阶段(跟踪了大部分代码,由于对c++不熟,没有找到源头),可以理解为,所有的reference对象在创建时是被特殊对待的,相应的对象结构由 InstanceRefKlass 来持有,因此在gc的过程中,会触发所有对象的 oop_follow_contents 操作,此操作可以认为是对一些额外对象的处理工作。在refClass的处理逻辑中,会调用ReferenceProcessor::discover_reference方法(文件referenceProcessor.cpp),此方法的的作用就在于将相应的对象进行添加到discoveredList当中。其相应的调用主逻辑如下所示

bool ReferenceProcessor::discover_reference(oop obj, ReferenceType rt) {
  // Make sure we are discovering refs (rather than processing discovered refs).
  // We only discover active references.

  //软引用如果还不需要回收,则直接返回
  if (rt == REF_SOFT) {
  }

  // Get the right type of discovered queue head.
  //找到之前的几种引用类型的链表
  DiscoveredList* list = get_discovered_list(rt);

  //采用多线程处理方式(并不表示以下的操作是多线程工作的)添加到链表当中
  if (_discovery_is_mt) {
    add_to_discovered_list_mt(*list, obj, discovered_addr);
  return true;
}

然后切换到相应的add_to_discovered_list方法,简要的逻辑如下所示:

ReferenceProcessor::add_to_discovered_list_mt(DiscoveredList& refs_list,
                                              oop             obj,
                                              HeapWord*       discovered_addr) {
  //找到头节点                                              
  oop current_head = refs_list.head();
  oop next_discovered = (current_head != NULL) ? current_head : obj;
  //尝试判断此obj的discovered对象是否仍是null,如果不是则表示另一个线程已经处理了
  oop retest = oopDesc::atomic_compare_exchange_oop(next_discovered, discovered_addr,
                                                    NULL);
  if (retest == NULL) {
    //进行正式的更改操作,即将obj重新设置为list中的头节点,长度+1
    refs_list.set_head(obj);
    refs_list.inc_length(1);

    //这里的_discovered_list_needs_barrier值为true, 则下面的操作即表示设置obj的 discovered 对象为之前的头节点,这样即形成一个后进先出的处理链条,即与Reference结构相对应
    if (_discovered_list_needs_barrier) {
      _bs->write_ref_field((void*)discovered_addr, next_discovered);
    }
  }

在上面的处理逻辑中,可以看出在jvm内部,并没有针对Reference重新建立相应的处理结构来维护相应的处理链,而是直接采用java中的Reference对象链来处理,只不过这些对象的关系由jvm在内部进行处理,而且这些处理的对象的内部结构因为也没有在被java其它对象所访问。

在java中 discovered对象只会被方法 tryHandlePending 修改,而此方法只会处理pending链中的对象。而在上面的处理过程中,相应的对象并没有在pending中,因此两个处理过程是不相干的。

在完成了相应的对象入栈之后,下一个阶段就是正式的引用放到pending中的过程。而在这个过程中,有些对象还需要被重新标记或处理。

cms执行部分

在之前的引用入栈之后,在cms的FinalMarking阶段,会进行各项引用的处理工作,即重新处理引用信息,然后附到pending上去。整个处理逻辑以及调用链如下所示:

FinalMarking 最终标识阶段
VM_CMS_Final_Remark 进行最终标识这一步骤
do_CMS_operation 进行指定的操作
CMS_op_checkpointRootsFinal 指定步骤语义
checkpointRootsFinal
checkpointRootsFinalWork
refProcessingWork 整个引用处理逻辑
enqueue_discovered_references 对enqueue_discovered_ref_helper的封装调用
enqueue_discovered_ref_helper 辅助工具类 找到pending节点,准备替换,然后又切换回来
enqueue_discovered_reflists 正式的替换pending节点

进入到标记阶段

在方法 CMSCollector::collect_in_background (文件concurrentMarkSweepGeneration.cpp)的处理中,封装了主要的处理逻辑,通过case来进入到不同的处理逻辑。我们这里关心的步骤为FinalMarking,这一阶段,相应的逻辑会切换到 VM_CMS_Final_Remark 这一个处理过程。代码如下所示:

case FinalMarking:
  {
    ReleaseForegroundGC x(this);

    VM_CMS_Final_Remark final_remark_op(this);
    VMThread::execute(&final_remark_op);
  }

在 VM_CMS_Final_Remark (文件vmCMSOperations.cpp) 的过程中,在一些简单处理之后,又将逻辑交给 CMSCollector::do_CMS_operation (文件concurrentMarkSweepGeneration.cpp) 来进行,然后里面又根据当前语义阶段,判定,最终进入到 CMS_op_checkpointRootsFinal 阶段,相应的代码如下简单所示:

void CMSCollector::do_CMS_operation(CMS_op_type op, GCCause::Cause gc_cause) {
  gclog_or_tty->date_stamp(PrintGC && PrintGCDateStamps);

  switch (op) {
    case CMS_op_checkpointRootsFinal: {
      checkpointRootsFinal(true,    // asynch
                           false,   // !clear_all_soft_refs
                           false);  // !init_mark_was_synchronous
      if (PrintGC) {
        _cmsGen->printOccupancy("remark");
      }
}

在相应的逻辑中,一些简单工作之后,又切换至 checkpointRootsFinalWork 方法当中,此方法承担了在最终回收之前的大部分工作。这里我们仅关心引用如何处理这一段,相应的方法简要如下所示:

{
  NOT_PRODUCT(GCTraceTime ts("refProcessingWork", PrintGCDetails, false, _gc_timer_cm);)
  refProcessingWork(asynch, clear_all_soft_refs);
}

即会进入到引用处理工作当中,这里的clear_all_soft即表示是否要清除softReference中的对象的相应逻辑(有一些策略在里面)。

主要的引用处理工作,包括2个部分,一个是对引用对象的处理,如对一些重新有效的对象需要排除在引用链外,或者是软引用清除,以及weak引用设置引用为null等。另一个步骤则是正式的pending对接工作。整个引用的处理代码如下所示:

void CMSCollector::refProcessingWork(bool asynch, bool clear_all_soft_refs) {

  ResourceMark rm;
  HandleMark   hm;

  // 重新设置相应的软引用清除策略
  // Process weak references.
  rp->setup_policy(clear_all_soft_refs);
  verify_work_stacks_empty();

  {
    //引用处理过程
    GCTraceTime t("weak refs processing", PrintGCDetails, false, _gc_timer_cm);

    ReferenceProcessorStats stats;
    //正式的引用处理过程
      stats = rp->process_discovered_references(&_is_alive_closure,
                                        &cmsKeepAliveClosure,
                                        &cmsDrainMarkingStackClosure,
                                        NULL,
                                        _gc_timer_cm);
  }

  if (rp->processing_is_mt()) {
  //因为之前收集过程是多线程的,这里引用链可能并不平(即每个链长度可能并不相同)
    rp->balance_all_queues();

    //正式的引用链重新附到pending对象上
    CMSRefProcTaskExecutor task_executor(*this);
    rp->enqueue_discovered_references(&task_executor);
  }
}

引用处理过程process_discovered_references

文件为referenceProcessor.cpp,相应的处理过程即针对每一种引用,通过统一的调用逻辑来进行处理,相应的调用处理简单如下所示:

ReferenceProcessorStats ReferenceProcessor::process_discovered_references(
  BoolObjectClosure*           is_alive,
  OopClosure*                  keep_alive,
  VoidClosure*                 complete_gc,
  AbstractRefProcTaskExecutor* task_executor,
  GCTimer*                     gc_timer) {

  // Soft references 处理软引用,传入了相应的软引用清除策略
  size_t soft_count = 0;
  {
    GCTraceTime tt("SoftReference", trace_time, false, gc_timer);
    soft_count =
      process_discovered_reflist(_discoveredSoftRefs, _current_soft_ref_policy, true,
                                 is_alive, keep_alive, complete_gc, task_executor);
  }

  // Weak references,逻辑与软引用相同,不过这里明确表示清除引用对象(即在程序中通过queue拿到的对象中的referent对象肯定为null)
  size_t weak_count = 0;
  {
    GCTraceTime tt("WeakReference", trace_time, false, gc_timer);
    weak_count =
      process_discovered_reflist(_discoveredWeakRefs, NULL, true,
                                 is_alive, keep_alive, complete_gc, task_executor);
  }

  // Final references 处理finalize对象
  // Phantom references 处理phantomReference对象
  // Weak global JNI references

  return ReferenceProcessorStats(soft_count, weak_count, final_count, phantom_count);
}

相应的处理过程又分为3个阶段。即可理解为并不是所有的对象都需要放到pending中,这里即是将不需要放到pending中的对象移除掉(下一次gc再处理)。相应的处理过程如下代码所示

ReferenceProcessor::process_discovered_reflist(
  DiscoveredList               refs_lists[],
  ReferencePolicy*             policy,
  bool                         clear_referent,
  BoolObjectClosure*           is_alive,
  OopClosure*                  keep_alive,
  VoidClosure*                 complete_gc,
  AbstractRefProcTaskExecutor* task_executor)
{

  // Phase 1 (soft refs only):
  // . Traverse the list and remove any SoftReferences whose
  //   referents are not alive, but that should be kept alive for
  //   policy reasons. Keep alive the transitive closure of all
  //   such referents.
  //如上注解所说,即有的软引用并不需要被处理,因此需要从链中排除掉
        process_phase1(refs_lists[i], policy,
                       is_alive, keep_alive, complete_gc);

  // Phase 2:
  // . Traverse the list and remove any refs whose referents are alive.
  // 有些reference对象在之前的标记中又是alive了,因此需要排除掉

      process_phase2(refs_lists[i], is_alive, keep_alive, complete_gc);

  // Phase 3:
  // . Traverse the list and process referents as appropriate.
  // 如果需要设置referent为null,则在第3个阶段处理
    RefProcPhase3Task phase3(*this, refs_lists, clear_referent, true /*marks_oops_alive*/);
    task_executor->execute(phase3);

  return total_list_count;
}

引用重新附到pending上 enqueue_discovered_references

相应的逻辑先通过转向 enqueue_discovered_ref_helper,然后最终进入到 ReferenceProcessor::enqueue_discovered_reflists 中(文件referenceProcessor.cpp). 相应的逻辑很简单,如下所示:

enqueue_discovered_reflist(_discovered_refs[i], pending_list_addr);
_discovered_refs[i].set_head(NULL);
_discovered_refs[i].set_length(0);

可以理解为即将相应的引用链附到pending上,然后 当前处理链清空(设置head=null以及length为0),附到pending上的过程,可以理解为就是将原来Reference中的pending和当前的处理链对接起来即可。因为两者都是reference对象,因此相应的处理可以理解为找到当前处理链中的末尾对象,然后设置末尾的discovered为pending,并且将pending重新修改为处理链的头节点即可。对接过程可以理解为 pending = jvmRList+pending这个过程。相应的详细代码如下所示:

void ReferenceProcessor::enqueue_discovered_reflist(DiscoveredList& refs_list,
                                                    HeapWord* pending_list_addr) {
  // 以下的注解即可相应的处理逻辑
  // Given a list of refs linked through the "discovered" field
  // (java.lang.ref.Reference.discovered), self-loop their "next" field
  // thus distinguishing them from active References, then
  // prepend them to the pending list.

  if (TraceReferenceGC && PrintGCDetails) {
    gclog_or_tty->print_cr("ReferenceProcessor::enqueue_discovered_reflist list "
                           INTPTR_FORMAT, (address)refs_list.head());
  }

  oop obj = NULL;
  oop next_d = refs_list.head();
    // Walk down the list, self-looping the next field
    // so that the References are not considered active.
    while (obj != next_d) {
      obj = next_d;
      next_d = java_lang_ref_Reference::discovered(obj);

      //这里为pending状态,因此设置相应的next指针
      // Self-loop next, so as to make Ref not active.
      java_lang_ref_Reference::set_next(obj, obj);

      if (next_d == obj) {  // obj is last next next_discover=自己,即当前节点为最后一个节点
        // Swap refs_list into pendling_list_addr and
        // set obj's discovered to what we read from pending_list_addr.
        //这里即重新设置pending值,即指向处理链的头节点
        oop old = oopDesc::atomic_exchange_oop(refs_list.head(), pending_list_addr);
        // Need oop_check on pending_list_addr above;
        // see special oop-check code at the end of
        // enqueue_discovered_reflists() further below.
        //当前处理链尾节点设置next_discover节点为pending节点
        java_lang_ref_Reference::set_discovered(obj, old); // old may be NULL
      }
    }
}

总结:

经过与整个gc过程相对接,整个引用的处理过程由jvm最终结束为Reference中的pending,由整个过程可以看出。在整个gc过程中,jvm针对各种弱引用对象的特殊处理,包括对象类型class的特殊对待,然后是各个过程过程中的特殊标识过程,最后是与java中queue的链接,最终完成整个弱引用处理。

通过了解jvm内部c++代码的实现过程,也可以更清楚地了解jvm内部的工作原理,一些流程也更方便地在后续对各种弱引用的处理过程更加了解,运用时也更加熟悉。

最后,附一个简单的java代码

public class T {
    private static Set<WeakReference> set = Sets.newIdentityHashSet();
    public static ReferenceQueue queue = new ReferenceQueue();

    private byte[] bytes = new byte[1024_000];
    private WeakReference reference = new WeakReference<T>(this, queue); //1
//    private WeakReference reference = new WeakReference<T>(this, queue) {}; //2

    public T() {
        set.add(reference); //3
        //4 注释掉上一行
    }

    public static void main(String[] args) throws Exception {
        for(int i = 0; i < 10_000; i++) {
            new T();
        }

        int i = 0;
        while(queue.remove(2000) != null) {
            i++;
        }

        System.out.println("->" + i);
    }
}

在以上的代码中,代码点 1和2 3和4 ,分别切换注释。相应的的运行结果都会不一样。可以了解一下不同的运行结果。



相关文章

发表评论

Comment form

(*) 表示必填项

还没有评论。

跳到底部
返回顶部