前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java中Reference的实现机制

Java中Reference的实现机制

作者头像
KINGYT
发布2023-03-15 13:42:51
5590
发布2023-03-15 13:42:51
举报

本文将从源码角度分析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对象及其相关字段,决定是否做进一步的处理。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-01-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 卯时卯刻 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档