ReferenceQueue的分析

阅读更多
之前看过hongjiang对ReferenceQueue的分析 http://hongjiang.info/java-referencequeue/,很赞的分析水准。仔细看完后,总是感觉有几个点还不是非常透彻,现在揣测着补充一下。
一.Reference类的pending成员
    pending是由jvm来赋值的,当Reference内部的referent对象的可达状态改变时,jvm会将Reference对象放入pending链表。
实现的逻辑在referenceProcessor.cpp的enqueue_discovered_reflist方法
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) chain them through the
  // "next" field (java.lang.ref.Reference.next) and prepend
  // 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 = refs_list.head();
  // Walk down the list, copying the discovered field into
  // the next field and clearing it (except for the last
  // non-sentinel object which is treated specially to avoid
  // confusion with an active reference).
  while (obj != sentinel_ref()) {
    assert(obj->is_instanceRef(), "should be reference object");
    oop next = java_lang_ref_Reference::discovered(obj);
    if (TraceReferenceGC && PrintGCDetails) {
      gclog_or_tty->print_cr("        obj " INTPTR_FORMAT "/next " INTPTR_FORMAT,
                             obj, next);
    }
    assert(java_lang_ref_Reference::next(obj) == NULL,
           "The reference should not be enqueued");
    if (next == sentinel_ref()) {  // obj is last
      // Swap refs_list into pendling_list_addr and
      // set obj's next to what we read from pending_list_addr.
      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.
      if (old == NULL) {
        // obj should be made to point to itself, since
        // pending list was empty.
        java_lang_ref_Reference::set_next(obj, obj);
      } else {
        java_lang_ref_Reference::set_next(obj, old);
      }
    } else {
      java_lang_ref_Reference::set_next(obj, next);
    }
    java_lang_ref_Reference::set_discovered(obj, (oop) NULL);
    obj = next;
  }
}

不懂C++,但大致从上面的代码可以看出
1.pending实际上指向的是回收列表的header,所以pending属性是static;
2.next指向的是下一个Reference;
3.如果是最后一个节点(哨兵节点),next=自己;
4.设置Reference的discovered为空;

二.Reference类的discovered成员
discovered的赋值是在discover_reference方法中,其中一个分支是调用add_to_discovered_list_mt。
oop retest = oopDesc::atomic_compare_exchange_oop(current_head, discovered_addr,
                                                    NULL);
  if (retest == NULL) {
    // This thread just won the right to enqueue the object.
    // We have separate lists for enqueueing so no synchronization
    // is necessary.
    refs_list.set_head(obj);
    refs_list.inc_length(1);
    if (_discovered_list_needs_barrier) {
      _bs->write_ref_field((void*)discovered_addr, current_head);
    }

    if (TraceReferenceGC) {
      gclog_or_tty->print_cr("Enqueued reference (mt) (" INTPTR_FORMAT ": %s)",
                             obj, obj->blueprint()->internal_name());
    }
  } else {
    // If retest was non NULL, another thread beat us to it:
    // The reference has already been discovered...
    if (TraceReferenceGC) {
      gclog_or_tty->print_cr("Already enqueued reference (" INTPTR_FORMAT ": %s)",
                             obj, obj->blueprint()->internal_name());
    }
  }

1.将refs_list.head设置到discovered;
2.refs_list.set_head(obj),设置自己为refs_list的header,即refs_list的header指向最后回收的Reference;
3.通过步骤1和2实际上也形成了一个链表。这个链表最终会变成pending列表;

三.ReferenceHandler线程唤醒
大致理解了上面的逻辑,再看java.lang.ref.Reference的ReferenceHandler类的run方法就比较好理解了
public void run() {
    for (;;) {
        Reference r;
        synchronized (lock) {
	   if (pending != null) {
	       r = pending;
	       Reference rn = r.next;
	       pending = (rn == r) ? null :rn; 
//java_lang_ref_Reference::set_next(obj, obj), rn == r即最后的节点
	        r.next = r;  //解除链表的连接
	    } else {
	       try {
		  lock.wait();
	       } catch (InterruptedException x) { }
	           continue;
	   }
        }
	// Fast path for cleaners
        if (r instanceof Cleaner) {
            ((Cleaner)r).clean();
	   continue;
        }
        ReferenceQueue q = r.queue;
        if (q != ReferenceQueue.NULL) 
	   q.enqueue(r);
    }
}

如果pending不为null,则将pending进行enqueue,否则线程进入wait状态。进入wait状态后,ReferenceHandler怎么被唤醒呢?Reference的代码中没有看到唤醒的代码。这个问题也咨询过hongjiang,但没有给出满意的答复。
搜了一下Hotspot的代码,发现一些线索:
在\hotspot\src\share\vm\oops\instanceRefKlass.cpp中
void instanceRefKlass::release_and_notify_pending_list_lock(
  BasicLock *pending_list_basic_lock) {
  // we may enter this with pending exception set
  PRESERVE_EXCEPTION_MARK;  // exceptions are never thrown, needed for TRAPS argument
  //
  Handle h_lock(THREAD, java_lang_ref_Reference::pending_list_lock());
  assert(ObjectSynchronizer::current_thread_holds_lock(
           JavaThread::current(), h_lock),
         "Lock should be held");
  // Notify waiters on pending lists lock if there is any reference.
  if (java_lang_ref_Reference::pending_list() != NULL) {
    ObjectSynchronizer::notifyall(h_lock, THREAD);
  }
  ObjectSynchronizer::fast_exit(h_lock(), pending_list_basic_lock, THREAD);
  if (HAS_PENDING_EXCEPTION) CLEAR_PENDING_EXCEPTION;
}

这一段代码就包含了线程唤醒的操作。
比如在hotspot\src\share\vm\gc_implementation\shared\concurrentGCThread.cpp中,就有对release_and_notify_pending_list_lock的调用
    case releaseAndNotifyPLL: {
	assert(owned > 0, "Don't have PLL");
	instanceRefKlass::release_and_notify_pending_list_lock(&pll_basic_lock);
	debug_only(owned--;)
	break;
}

大致可以推测在GC时,如果发现java_lang_ref_Reference::pending_list() != NULL,会唤醒ReferenceHandler线程。

你可能感兴趣的:(java)