`
jnullpointer
  • 浏览: 15826 次
  • 性别: Icon_minigender_1
社区版块
存档分类
最新评论

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线程。
分享到:
评论

相关推荐

    线程池调用队列

    线程池调用队列是多线程编程中一个重要的概念,它在处理并发任务时起着关键作用。...此外,通过分析线程池的源码,可以帮助我们深入理解其内部工作机制,以便在遇到性能问题时能更精准地定位和解决。

    内存泄漏检测示例.zip

    2. **运行机制**:`LeakCanary`通过实现`ReferenceQueue`来监控已释放的引用。当检测到潜在的内存泄漏时,它会创建一个`HeapDump`,然后使用`HeapAnalyzer`分析堆转储文件,寻找可能的泄漏引用链。 3. **分析结果**...

    Java引用队列和虚引用实例分析

    Java 引用队列和虚引用实例分析 Java 引用队列和虚引用是 Java 语言中两种重要的引用类型,它们在程序中扮演着不同的角色。引用队列由 `ReferenceQueue` 类表示,用于保存被回收后对象的引用,而虚引用则是弱引用的...

    javabitset源码-JerrySoundCode:杰瑞声码

    bitset源码Java源码分析 基础集合列表 ArrayList (done) Vector (done) LinkedList (done) Stack (done) ReferenceQueue (done) ArrayDeque (done) Set HashSet (done) TreeSet (done) LinkedHashSet (done) BitSet ...

    java 引用相关文档

    工具如VisualVM或JProfiler可以帮助开发者监控和分析Java应用的内存状态,更好地理解引用类型的作用。 通过阅读“java 引用相关文档”,开发者可以深入理解Java引用机制,提升对内存管理的掌控能力,从而编写出更加...

    Java中的软引用弱引用和虚引用.docx

    本文将深入探讨Java中三种特殊的引用类型——软引用(Soft Reference)、弱引用(Weak Reference)以及虚引用(Phantom Reference),并分析它们如何帮助我们更好地管理内存资源。 #### 二、基础知识回顾 在深入了解这三...

    如何解决Java的循环引用问题

    由于Java的垃圾回收机制依赖于可达性分析,如果一个对象在任何执行路径上都无法访问,那么这个对象就会被视为不可达,进而被垃圾回收。但在循环引用的情况下,由于引用链内部的相互引用,即使对象在逻辑上不再被程序...

    15个顶级Java多线程面试题及回答.docx

    ### 15个顶级Java多线程面试题详解 #### 题目一:线程执行顺序控制 **题目:** 如何确保线程T2在T1执行完毕后执行,而T3在T2执行完毕后执行? **解答:** 在Java中可以通过`Thread.join()`方法来实现线程之间的顺序...

    jdk 1.6 gc详解

    在问题排查时,可以使用`jmap -heap [pid]`等工具监控内存状态,以及`-XX:+PrintGCDetails`等选项输出GC日志,以便分析和优化。 总结来说,理解JDK 1.6的GC机制对于优化Java应用程序的性能至关重要。通过了解内存...

    图文详解java内存回收机制

    - 虚引用:`PhantomReference`,不直接引用对象,仅用于跟踪对象被回收的状态,需要配合引用队列`ReferenceQueue`使用。 5. 垃圾收集器 - Serial GC:单线程的收集器,适用于客户端应用。 - ParNew GC:Serial ...

    浅谈Java 中的引用类型

    虚引用与软引用、弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,会在垃圾回收之前,将该引用加入到与之对应关联的引用队列中。...

    java四种引用及在LeakCanery中应用详解

    虚引用的主要作用是在对象被回收前加入到引用队列(ReferenceQueue),以便于在对象被回收后执行某些清理操作。虚引用不会阻止对象的回收,也不关心内存状态,只提供一种通知机制。 在LeakCanary这样的内存泄漏检测...

Global site tag (gtag.js) - Google Analytics