Java 源码 - java.lang.ref.Reference

218 阅读8分钟

Reference

主要是负责内存的一个状态,当然它还和java虚拟机,垃圾回收器打交道。Reference类首先把内存分为4种状态Active,Pending,Enqueued,Inactive。根据jdk的定义

A Reference instance is in one of four possible internal states:

Active: Subject to special treatment by the garbage collector.  Some
time after the collector detects that the reachability of the
referent has changed to the appropriate state, it changes the
instance's state to either Pending or Inactive, depending upon
whether or not the instance was registered with a queue when it was
created.  In the former case it also adds the instance to the
pending-Reference list.  Newly-created instances are Active.

Pending: An element of the pending-Reference list, waiting to be
enqueued by the Reference-handler thread.  Unregistered instances
are never in this state.

Enqueued: An element of the queue with which the instance was
registered when it was created.  When an instance is removed from
its ReferenceQueue, it is made Inactive.  Unregistered instances are
never in this state.

Inactive: Nothing more to do.  Once an instance becomes Inactive its
state will never change again.

The state is encoded in the queue and next fields as follows:

Active: queue = ReferenceQueue with which instance is registered, or
ReferenceQueue.NULL if it was not registered with a queue; next =
null.

Pending: queue = ReferenceQueue with which instance is registered;
next = this

Enqueued: queue = ReferenceQueue.ENQUEUED; next = Following instance
in queue, or this if at end of list.

Inactive: queue = ReferenceQueue.NULL; next = this.

With this scheme the collector need only examine the next field in order
to determine whether a Reference instance requires special treatment: If
the next field is null then the instance is active; if it is non-null,
then the collector should treat the instance normally.

To ensure that a concurrent collector can discover active Reference
objects without interfering with application threads that may apply
the enqueue() method to those objects, collectors should link
discovered objects through the discovered field. The discovered
field is also used for linking Reference objects in the pending list.

Reference的状态集合

Reference源码中并不存在一个成员变量用于描述Reference的状态,它是通过组合判断referent、discovered、queue、next成员的存在性或者顺序"拼凑出"对应的状态,注释中描述如下:

一个引用对象可以同时存在两种状态: 

  • 第一组状态:"active", "pending", or "inactive" 

  • 第二组状态:"registered", "enqueued", "dequeued", or "unregistered" 

Active: 当前引用实例处于Active状态,会收到垃圾收集器的特殊处理。在垃圾收集器检测到referent的可达性已更改为适当状态之后的某个时间,垃圾收集器会"通知"当前引用实例改变其状态为"pending"或者"inactive"。此时的判断条件是:referent != null; discovered = null或者实例位于GC的discovered列表中。 

Pending: 当前的引用实例是pending-Reference列表的一个元素,等待被ReferenceHandler线程处理。pending-Reference列表通过应用实例的discovered字段进行关联。此时的判断条件是:referent = null; discovered = pending-Reference列表中的下一个元素 Inactive: 当前的引用实例处于非Active和非Pending状态。此时的判断条件是:referent = null (同时discovered = null) Registered: 当前的引用实例创建的时候关联到一个引用队列实例,但是引用实例暂未加入到队列中。此时的判断条件是:queue = 传入的ReferenceQueue实例 

Enqueued: 当前的引用实例已经添加到和它关联的引用队列中但是尚未移除(remove),也就是调用了ReferenceQueue.enqueued()后的Reference实例就会处于这个状态。此时的判断条件是:queue = ReferenceQueue.ENQUEUE; next = 引用列表中的下一个引用实例,或者如果当前引用实例是引用列表中的最后一个元素,则它会进入Inactive状态 Dequeued: 当前的引用实例曾经添加到和它关联的引用队列中并且已经移除(remove)。此时的判断条件是:queue = ReferenceQueue.NULL; next = 当前的引用实例 

Unregistered: 当前的引用实例不存在关联的引用队列,也就是创建引用实例的时候传入的queue为null。此时的判断条件是:queue = ReferenceQueue.NULL

注释中还强调了几点:

  • 初始化状态:[active/registered][active/unregistered](这种情况只限于FinalReferences)
  • 终结状态:[inactive/dequeued][inactive/unregistered]
  • 不可能出现的状态:[active/enqeued][active/dequeued]

ReferenceHandler其实是Reference中静态代码块中初始化的线程实例,主要作用是:处理pending状态的引用实例,使它们入队列并走向[inactive/dequeued]状态。另外,上面的线框图是分两部分,其中上半部分是使用了ReferenceQueue,后半部分是没有使用ReferenceQueue(或者说使用了ReferenceQueue.NULL)。这里尝试用PPT画一下简化的状态跃迁图:

构造函数

   Reference(T referent) {
        this(referent, null);
   }

   Reference(T referent, ReferenceQueue<? super T> queue) {
        this.referent = referent;
        this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
   }

构造函数依赖于一个泛型的referent成员以及一个ReferenceQueue<? super T>的队列,如果ReferenceQueue实例为null,则使用ReferenceQueue.NULL

主要内存成员变量

private T referent;       
volatile ReferenceQueue<? super T> queue;  

/* When active:   NULL 
 *    pending:    this 
 *    Enqueued:   next reference in queue (or this if last) 
 *    Inactive:   this 
 */  
@SuppressWarnings("rawtypes")  
Reference next;  

transient private Reference<T> discovered;  /* used by VM */  

/* List of References waiting to be enqueued.  The collector adds
 * References to this list, while the Reference-handler thread removes
 * them.  This list is protected by the above lock object. The
 * list uses the discovered field to link its elements.
 */
private static Reference<Object> pending = null;
  • referent表示其引用的对象,即在构造的时候需要被包装在其中的对象。
  • queue 是Reference对象关联的队列,也就是引用队列,对象如果即将被垃圾收集器回收,此队列作为通知的回调队列,也就是当Reference实例持有的对象referent要被回收的时候,Reference实例会被放入引用队列,那么程序执行的时候可以从引用队列得到或者监控相应的Reference实例。
  • next 即当前引用节点所存储的下一个即将被处理的节点。但 next 仅在放到queue中才会有意义,因为只有在enqueue的时候,会将next设置为下一个要处理的Reference对象。为了描述相应的状态值,在放到队列当中后,其queue就不会再引用这个队列了。而是引用一个特殊的 ENQUEUED(内部定义的一个空队列)。因为已经放到队列当中,并且不会再次放到队列当中。所以next其实是下一个Reference实例的引用,Reference实例通过此构造单向的链表。
  • discovered 表示要处理的对象的下一个对象。即可以理解要处理的对象也是一个链表,通过discovered进行排队,这边只需要不停地拿到pending,然后再通过discovered 不断地拿到下一个对象赋值给pending即可,直到取到了最有一个。它是被JVM 使用的。注意这个属性由transient修饰,基于状态表示不同链表中的下一个待处理的对象,主要是pending-reference列表的下一个元素,通过JVM直接调用赋值。
  • pending 是等待被入队的引用列表。JVM 收集器会添加引用到这个列表,直到Reference-handler线程移除了它们。这个列表使用 discovered 字段来连接它下一个元素(即 pending 的下一个元素就是discovered对象。r = pending; pending = r.discovered)。

Reference 的静态代码块

static {
    ThreadGroup tg = Thread.currentThread().getThreadGroup();
    for (ThreadGroup tgn = tg;
         tgn != null;
         tg = tgn, tgn = tg.getParent());
    Thread handler = new ReferenceHandler(tg, "Reference Handler");
    /* If there were a special system-only priority greater than
     * MAX_PRIORITY, it would be used here
     */
    handler.setPriority(Thread.MAX_PRIORITY);
    handler.setDaemon(true);
    handler.start();

    // provide access in SharedSecrets
    SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
        @Override
        public boolean tryHandlePendingReference() {
            return tryHandlePending(false);
        }
    });
}

我们,当 Refrence 类被加载的时候,会执行静态代码块。在静态代码块里面,会启动 ReferenceHandler 线程,并设置线程的级别为最大级别, Thread.MAX_PRIORITY。

由于ReferenceHandler线程是Reference的静态代码创建的,所以只要Reference这个父类被初始化,该线程就会创建和运行,由于它是守护线程,除非JVM进程终结,否则它会一直在后台运行。

静态类ReferenceHandler

接下来我们来看一下 ReferenceHandler 这个类,可以看到 run 方法里面是一个死循环,我们主要关注 tryHandlePending 方法就 Ok 了

private static class ReferenceHandler extends Thread {

   ----- // 核心代码如下

    public void run() {
        while (true) {
            tryHandlePending(true);
        }
    }
}
static boolean tryHandlePending(boolean waitForNotify) {
    Reference<Object> r;
    Cleaner c;
    try {
        synchronized (lock) {
            // 检查 pending 是否为 null,不为 null,制定 pending enqueue
            if (pending != null) {
                r = pending;
                // 'instanceof' might throw OutOfMemoryError sometimes
                // so do this before un-linking 'r' from the 'pending' chain...
                c = r instanceof Cleaner ? (Cleaner) r : null;
                // unlink 'r' from 'pending' chain
                pending = r.discovered;
                r.discovered = null;
            } else { // 为 null。等待
                // The waiting on the lock may cause an OutOfMemoryError
                // because it may try to allocate exception objects.
                if (waitForNotify) {
                    lock.wait();
                }
                // retry if waited
                return waitForNotify;
            }
        }
    } catch (OutOfMemoryError x) {
        // Give other threads CPU time so they hopefully drop some live references
        // and GC reclaims some space.
        // Also prevent CPU intensive spinning in case 'r instanceof Cleaner' above
        // persistently throws OOME for some time...
        Thread.yield();
        // retry
        return true;
    } catch (InterruptedException x) {
        // retry
        return true;
    }

    // Fast path for cleaners
    if (c != null) {
        c.clean();
        return true;
    }

    ReferenceQueue<? super Object> q = r.queue;
    if (q != ReferenceQueue.NULL) q.enqueue(r);
    return true;
}

在 tryHandlePending 方法里面,检查 pending 是否为 null,如果pending不为 null,则将 pending 进行 enqueue,否则线程进入 wait 状态。

问题来了,我们从 Reference 源码中发现没有给 discovered和 pending 赋值的地方,那 pending和 discovered 到底是谁给他们赋值的。

我们回头再来看一下注释:简单来说,垃圾回收器会把 References 添加进入,Reference-handler thread 会移除它,即 discovered和 pending 是由垃圾回收器进行赋值的

/* List of References waiting to be enqueued.  The collector adds
 * References to this list, while the Reference-handler thread removes
 * them.  This list is protected by the above lock object. The
 * list uses the discovered field to link its elements.
 */
private static Reference<Object> pending = null;