本文将从源码角度分析Java中Reference的实现机制。OpenJDK版本:
➜ jdk hg id 76072a077ee1 jdk-11 28
Java中的Reference机制基本上都是围绕Java类java.lang.ref.Reference来实现的,其子类有
java.lang.ref.SoftReference
java.lang.ref.WeakReference
java.lang.ref.PhantomReference
java.lang.ref.FinalReference
其中 java.lang.ref.FinalReference 类仅供JDK内部使用,和 java.lang.ref.Finalizer 类一起用于实现 Object.finalize 方法的调用(该方法现已不推荐使用)。
其他三个类分别对应了Java对象的三种 reachability 级别。其适用场景可以参考 Java的API文档:
Soft references are for implementing memory-sensitive caches, weak references are for implementing canonicalizing mappings that do not prevent their keys (or values) from being reclaimed, and phantom references are for scheduling post-mortem cleanup actions.
我们首先看下java.lang.ref.Reference类的构造函数:
代码语言:javascript复制private T referent; /* Treated specially by GC */
/* The queue this reference gets enqueued to by GC notification or by
* calling enqueue()...
*/
volatile ReferenceQueue<? super T> queue;
/**
* Returns this reference object's referent. If this reference object has
* been cleared, either by the program or by the garbage collector, then
* this method returns <code>null</code>...
*/
public T get() {
return this.referent;
}
Reference(T referent) {
this(referent, null);
}
Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}
由上面代码我们可以看到,在创建Reference时,我们可以传入referent和queue参数。Reference虽然持有referent的引用,但由于其特殊性,它并不能阻止referent被GC回收。当referent被GC回收之后,如果queue不为null,该Reference对象会被放到这个queue里。
接下来,我们再来看下java.lang.ref.Reference类的static代码块
代码语言:javascript复制static {
...
Thread handler = new ReferenceHandler(tg, "Reference Handler");
...
handler.setPriority(Thread.MAX_PRIORITY);
handler.setDaemon(true);
handler.start();
...
}
该static代码块启动了一个名为Reference Handler的线程。我们可以用jstack命令确认下它是存在的
代码语言:javascript复制➜ ~ jstack 3656
...
"Reference Handler" #2 daemon prio=10 os_prio=0 cpu=0.33ms elapsed=49.35s tid=0x00007f15b0124800 nid=0x770e waiting on condition [0x00007f159433b000]
java.lang.Thread.State: RUNNABLE
at java.lang.ref.Reference.waitForReferencePendingList(java.base@11/Native Method)
at java.lang.ref.Reference.processPendingReferences(java.base@11/Reference.java:241)
at java.lang.ref.Reference$ReferenceHandler.run(java.base@11/Reference.java:213)
...
由上可见,该线程确实是存在的。我们再看下该线程干了什么事
代码语言:javascript复制/* High-priority thread to enqueue pending References
*/
private static class ReferenceHandler extends Thread {
...
public void run() {
while (true) {
processPendingReferences();
}
}
}
该线程会一直调用processPendingReferences 方法,直到该Java进程关闭。看下该方法
代码语言:javascript复制/*
* Atomically get and clear (set to null) the VM's pending-Reference list.
*/
private static native Reference<Object> getAndClearReferencePendingList();
...
/*
* Wait until the VM's pending-Reference list may be non-null.
*/
private static native void waitForReferencePendingList();
...
private static void processPendingReferences() {
...
waitForReferencePendingList();
Reference<Object> pendingList;
synchronized (processPendingLock) {
pendingList = getAndClearReferencePendingList();
...
}
while (pendingList != null) {
Reference<Object> ref = pendingList;
pendingList = ref.discovered;
ref.discovered = null;
if (ref instanceof Cleaner) {
((Cleaner)ref).clean();
...
} else {
ReferenceQueue<? super Object> q = ref.queue;
if (q != ReferenceQueue.NULL) q.enqueue(ref);
}
}
...
}
其逻辑也比较简单,该方法会先调用waitForReferencePendingList方法,阻塞等待,直到pending-Reference list不为空,接着调用getAndClearReferencePendingList方法获取这个list,最后遍历这个list中的所有Reference对象,如果这个Reference对象的queue字段不为null,就把这个Reference对象enqueue到这个queue中。
接下来我们再看下waitForReferencePendingList和getAndClearReferencePendingList这两个native方法。
C文件src/java.base/share/native/libjava/Reference.c
代码语言:javascript复制JNIEXPORT jobject JNICALL
Java_java_lang_ref_Reference_getAndClearReferencePendingList(JNIEnv *env, jclass ignore)
{
return JVM_GetAndClearReferencePendingList(env);
}
JNIEXPORT void JNICALL
Java_java_lang_ref_Reference_waitForReferencePendingList(JNIEnv *env, jclass ignore)
{
JVM_WaitForReferencePendingList(env);
}
继续看下对应的JVM方法
C 文件src/hotspot/share/prims/jvm.cpp
代码语言:javascript复制JVM_ENTRY(jobject, JVM_GetAndClearReferencePendingList(JNIEnv* env))
JVMWrapper("JVM_GetAndClearReferencePendingList");
MonitorLockerEx ml(Heap_lock);
oop ref = Universe::reference_pending_list();
if (ref != NULL) {
Universe::set_reference_pending_list(NULL);
}
return JNIHandles::make_local(env, ref);
JVM_END
JVM_ENTRY(void, JVM_WaitForReferencePendingList(JNIEnv* env))
JVMWrapper("JVM_WaitForReferencePendingList");
MonitorLockerEx ml(Heap_lock);
while (!Universe::has_reference_pending_list()) {
ml.wait();
}
JVM_END
继续找到对应的Universe中的方法
C 文件src/hotspot/share/memory/universe.cpp
代码语言:javascript复制oop Universe::reference_pending_list() {
...
return _reference_pending_list;
}
void Universe::set_reference_pending_list(oop list) {
...
_reference_pending_list = list;
}
bool Universe::has_reference_pending_list() {
...
return _reference_pending_list != NULL;
}
由上可见,这些方法最终都使用了_reference_pending_list字段,我们再看下这个字段的定义
C 文件src/hotspot/share/memory/universe.hpp
代码语言:javascript复制// References waiting to be transferred to the ReferenceHandler
static oop _reference_pending_list;
根据这个字段的定义及其注释可知,该字段持有的oop列表,就是最终要传给Reference Handler线程的pending-Reference list。
至此,_reference_pending_list的处理部分都已经很清晰了。
接下来我们看下这些Reference又是怎样被添加到这个_reference_pending_list里的。
先说下大致流程,JVM在每一次的GC过程中,都会通过一定的方式,找到当前存活的java.lang.ref.Reference对象及其子类对象,根据Reference对象的 reachability 级别判断其字段referent引用的对象是否应该继续存活,如果不应该,referent字段所指的对象就会被垃圾回收,而该referent字段也会被置为null,最后,这个Reference对象会被添加到 _reference_pending_list 列表中。
具体流程我们来看下代码。
以G1 GC为例,不管Young GC, Concurrent Mark, 还是Full GC,在找到存活的Reference对象后,都会调用下面方法对Reference进行处理
C 文件src/hotspot/share/gc/shared/referenceProcessor.cpp
代码语言:javascript复制ReferenceProcessorStats ReferenceProcessor::process_discovered_references(
BoolObjectClosure* is_alive,
OopClosure* keep_alive,
VoidClosure* complete_gc,
AbstractRefProcTaskExecutor* task_executor,
ReferenceProcessorPhaseTimes* phase_times) {
...
{
RefProcTotalPhaseTimesTracker tt(RefPhase1, phase_times, this);
process_soft_ref_reconsider(is_alive, keep_alive, complete_gc,
task_executor, phase_times);
}
...
{
RefProcTotalPhaseTimesTracker tt(RefPhase2, phase_times, this);
process_soft_weak_final_refs(is_alive, keep_alive, complete_gc, task_executor, phase_times);
}
{
RefProcTotalPhaseTimesTracker tt(RefPhase3, phase_times, this);
process_final_keep_alive(keep_alive, complete_gc, task_executor, phase_times);
}
{
RefProcTotalPhaseTimesTracker tt(RefPhase4, phase_times, this);
process_phantom_refs(is_alive, keep_alive, complete_gc, task_executor, phase_times);
}
...
return stats;
}
由上面代码可以看到,该逻辑分别对soft、weak、phantom和final类型的Reference做了处理。我们以PhantomReference为例,看下其具体是怎么做的。
C 文件src/hotspot/share/gc/shared/referenceProcessor.cpp
void ReferenceProcessor::process_phantom_refs(BoolObjectClosure* is_alive,
OopClosure* keep_alive,
VoidClosure* complete_gc,
AbstractRefProcTaskExecutor* task_executor,
ReferenceProcessorPhaseTimes* phase_times) {
...
if (_processing_is_mt) {
...
} else {
...
for (uint i = 0; i < _max_num_queues; i ) {
removed = process_phantom_refs_work(_discoveredPhantomRefs[i], is_alive, keep_alive, complete_gc);
}
...
}
...
}
该方法最终调用了process_phantom_refs_work方法,继续看下这个方法
代码语言:javascript复制size_t ReferenceProcessor::process_phantom_refs_work(DiscoveredList& refs_list,
BoolObjectClosure* is_alive,
OopClosure* keep_alive,
VoidClosure* complete_gc) {
DiscoveredListIterator iter(refs_list, keep_alive, is_alive);
while (iter.has_next()) {
iter.load_ptrs(...);
oop const referent = iter.referent();
if (referent == NULL || iter.is_referent_alive()) {
...
iter.remove();
iter.move_to_next();
} else {
iter.clear_referent();
...
iter.next();
}
}
iter.complete_enqueue();
...
refs_list.clear();
return iter.removed();
}
在该方法中,refs_list参数表示这次GC检测到的所有存活的PhantomReference对象。该方法会遍历这些对象,并检查其持有的referent是否存活,如果存活,就从该list中移除,如果不存活,就把该PhantomReference对象的referent字段设置为null,然后再处理下一个。
在所有PhantomReference对象都处理完之后,此时refs_list队列中存放的都是已经被GC回收了referent对象的PhantomReference对象,且PhantomReference对象的referent字段都已经为null。
最后再调用iter.complete_enqueue()方法,这个方法会把refs_list中所有的Reference全都加到_reference_pending_list队列中,看下具体代码
代码语言:javascript复制void DiscoveredListIterator::complete_enqueue() {
if (_prev_discovered != NULL) {
...
oop old = Universe::swap_reference_pending_list(_refs_list.head());
HeapAccess<AS_NO_KEEPALIVE>::oop_store_at(_prev_discovered, java_lang_ref_Reference::discovered_offset, old);
}
}
再看下 Universe::swap_reference_pending_list方法的实现。
C 文件src/hotspot/share/memory/universe.cpp
代码语言:javascript复制oop Universe::swap_reference_pending_list(oop list) {
assert_pll_locked(is_locked);
return Atomic::xchg(list, &_reference_pending_list);
}
由上面两段代码可以看到,_reference_pending_list字段最终指向了_refs_list列表的head,而_reference_pending_list字段原来指向的列表则被append到_refs_list列表之后,即,在调用了DiscoveredListIterator::complete_enqueue方法之后,_refs_list列表被添加到了_reference_pending_list中。
到现在为止,整个流程算是全部完整了。
我们再梳理下整个流程:
首先,java.lang.ref.Reference类在初始化时,会启动一个Reference Handler线程,该线程会调用waitForReferencePendingList这个native方法阻塞等待,直到JVM中的_reference_pending_list字段不为null。
JVM中,每一次GC过程都会找出当前存活的Reference对象,并检查其引用的referent对象是否存活,如果没有存活了,就会把该Reference对象的referent字段置为null,并把这个Reference对象放到_reference_pending_list列表中。
当_reference_pending_list列表有数据后,JVM会通知Reference Handler线程,使其从等待中返回。之后,该线程会调用getAndClearReferencePendingList这个native方法,获取_reference_pending_list列表,并把_reference_pending_list字段置为null。
获取_reference_pending_list列表后,该线程会遍历这个列表中的所有Reference对象,检查其queue字段是否不为null,如果是,就会把该Reference对象enquene到这个queue中。
最后,业务逻辑检查对应的ReferenceQueue,根据ReferenceQueue返回的Reference对象及其相关字段,决定是否做进一步的处理。