之前看过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线程。
分享到:
相关推荐
线程池调用队列是多线程编程中一个重要的概念,它在处理并发任务时起着关键作用。...此外,通过分析线程池的源码,可以帮助我们深入理解其内部工作机制,以便在遇到性能问题时能更精准地定位和解决。
2. **运行机制**:`LeakCanary`通过实现`ReferenceQueue`来监控已释放的引用。当检测到潜在的内存泄漏时,它会创建一个`HeapDump`,然后使用`HeapAnalyzer`分析堆转储文件,寻找可能的泄漏引用链。 3. **分析结果**...
Java 引用队列和虚引用实例分析 Java 引用队列和虚引用是 Java 语言中两种重要的引用类型,它们在程序中扮演着不同的角色。引用队列由 `ReferenceQueue` 类表示,用于保存被回收后对象的引用,而虚引用则是弱引用的...
bitset源码Java源码分析 基础集合列表 ArrayList (done) Vector (done) LinkedList (done) Stack (done) ReferenceQueue (done) ArrayDeque (done) Set HashSet (done) TreeSet (done) LinkedHashSet (done) BitSet ...
工具如VisualVM或JProfiler可以帮助开发者监控和分析Java应用的内存状态,更好地理解引用类型的作用。 通过阅读“java 引用相关文档”,开发者可以深入理解Java引用机制,提升对内存管理的掌控能力,从而编写出更加...
本文将深入探讨Java中三种特殊的引用类型——软引用(Soft Reference)、弱引用(Weak Reference)以及虚引用(Phantom Reference),并分析它们如何帮助我们更好地管理内存资源。 #### 二、基础知识回顾 在深入了解这三...
由于Java的垃圾回收机制依赖于可达性分析,如果一个对象在任何执行路径上都无法访问,那么这个对象就会被视为不可达,进而被垃圾回收。但在循环引用的情况下,由于引用链内部的相互引用,即使对象在逻辑上不再被程序...
### 15个顶级Java多线程面试题详解 #### 题目一:线程执行顺序控制 **题目:** 如何确保线程T2在T1执行完毕后执行,而T3在T2执行完毕后执行? **解答:** 在Java中可以通过`Thread.join()`方法来实现线程之间的顺序...
在问题排查时,可以使用`jmap -heap [pid]`等工具监控内存状态,以及`-XX:+PrintGCDetails`等选项输出GC日志,以便分析和优化。 总结来说,理解JDK 1.6的GC机制对于优化Java应用程序的性能至关重要。通过了解内存...
- 虚引用:`PhantomReference`,不直接引用对象,仅用于跟踪对象被回收的状态,需要配合引用队列`ReferenceQueue`使用。 5. 垃圾收集器 - Serial GC:单线程的收集器,适用于客户端应用。 - ParNew GC:Serial ...
虚引用与软引用、弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,会在垃圾回收之前,将该引用加入到与之对应关联的引用队列中。...
虚引用的主要作用是在对象被回收前加入到引用队列(ReferenceQueue),以便于在对象被回收后执行某些清理操作。虚引用不会阻止对象的回收,也不关心内存状态,只提供一种通知机制。 在LeakCanary这样的内存泄漏检测...