深入理解Java中的引用(一)——Reference

深入理解Java中的引用(一)——Reference

本系列文章首先会介绍Reference类,为之后介绍的强引用、软引用、弱引用和虚引用打下基础。
最后会介绍虚引用在DirectBuffer回收中的应用。

引用(Reference)

在介绍四种不同类型的引用之前先看一下他们的父类:java.lang.ref.Reference。看以看到Reference 有五个成员变量:referent,queue,pending,next,discovered。下面会一一介绍各个成员变量的作用。

Reference状态

在介绍五个成员变量之前,首页要明确一点,Reference是有状态的,Reference对象的一共有四种状态。如下图所示


image.png

Reference对象所处状态不同,成员变量的值也会变化。

  • Active: Reference对象新建时处于该状态。
  • Pending: 当Reference包装的referent = null的时候,JVM会把Reference设置成pending状态。如果Reference创建时指定了ReferenceQueue,那么会被ReferenceHandler线程处理进入到ReferenceQueue队列中,如果没有就进入Inactive状态。
  • Enqueue: 进入ReferenceQueue中的对象,等待被回收
  • Inactive: Reference对象从ReferenceQueue取出来并被处理掉。处于Inactive的Reference对象状态不能再改变

核心成员变量

1)referent: 表示被包装的对象
下面代码中new Object()就是被包装的对象。

WeakReference wo = new WeakReference(new Object());
 
 

2) queue: 表示被包装的对象被回收时,需要被通知的队列,该队列在Reference构造函数中指定。当referent被回收的时候,Reference对象就处在了Pending状态,Reference会被放入到该队列中,如果构造函数没有指定队列,那么就进入Inactive状态。

volatile ReferenceQueue queue;
......
    Reference(T referent) {
        this(referent, null);
    }

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

3)pending: 表示等待被加入到queue的Reference 列表。

    /* 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 pending = null;
 
 

pending理解链表有点费解,因为代码层面上看这明明就是Reference对象。其实当Reference处在Pending状态时,他的pending字段被赋值成了下一个要处理的对象(即下面讲的discovered),通过discovered可以拿到下一个对象并且赋值给pending,直到最后一个,所以这里就可以把它当成一个链表。而discovered是JVM的垃圾回收器添加进去的,大家可以不用关心底层细节。

4)discovered: 当处于Reference处在pending状态:discovered为pending集合中的下一个元素;其他状态:discovered为null

    /* When active:   next element in a discovered reference list maintained by GC (or this if last)
     *     pending:   next element in the pending list (or null if last)
     *   otherwise:   NULL
     */
    transient private Reference discovered;  /* used by VM */

上述discovered与pending的关系可以用下图表示

image.png

5) next: 当Reference对象在queue中时(即Reference处于Enqueued状态),next描述当前引用节点所存储的下一个即将被处理的节点。

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

ReferenceHandler线程会把pending状态的Reference放入ReferenceQueue中,上面说的next,discovered 字段在入队之后也会发生变化,下一小节会介绍。

ReferenceQueue入队过程

上面说到ReferenceHandler线程会把pending状态的Reference对象放入到ReferenceQueue队列中。
查看ReferenceQueue中入队源代码。

    boolean enqueue(Reference r) { /* Called only by Reference class */
        synchronized (lock) {
            // Check that since getting the lock this reference hasn't already been
            // enqueued (and even then removed)
            ReferenceQueue queue = r.queue;
            if ((queue == NULL) || (queue == ENQUEUED)) {
                return false;
            }
            assert queue == this;
            //设置queue状态
            r.queue = ENQUEUED;
            //改变next指针
            r.next = (head == null) ? r : head;
            head = r;
            queueLength++;
            if (r instanceof FinalReference) {
                sun.misc.VM.addFinalRefCount(1);
            }
            lock.notifyAll();
            return true;
        }
    }

可以看到入队的Reference节点r进入队列,Reference节点被放在队列头,所以这是一个先进后出队列。 入队的示意图如下:

image.png

ReferenceHandler线程

Reference类中另一个比较重要的成员是ReferenceHandler。ReferenceHandler是一个线程。当JVM加载Reference的时候,就会启动这个线程。用jstack查看该线程栈可以看到。Reference Handler是JVM中的2号线程,并且线性优先级被设置为高优先级

"Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007f7718075800 nid=0x10244 in Object.wait() [0x00007f7708363000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x00000000d55b9580> (a java.lang.ref.Reference$Lock)
    at java.lang.Object.wait(Object.java:502)
    at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
    - locked <0x00000000d55b9580> (a java.lang.ref.Reference$Lock)
    at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

看源代码他是如何工作的:

    private static class ReferenceHandler extends Thread {

        ReferenceHandler(ThreadGroup g, String name) {
            super(g, name);
        }

        public void run() {
            for (;;) {
                Reference r;
                synchronized (lock) {
                    if (pending != null) {
                        r = pending;
                        pending = r.discovered;
                        r.discovered = null;
                    } else {
                        try {
                            try {
                                lock.wait();
                            } catch (OutOfMemoryError x) { }
                        } 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);
            }
        }
    }
 
 

通过上面代码可以看到ReferenceHandler线程做的是不断的检查pending是否为null, 如果不为null,将pending对象进行入队操作,而pending的赋值由JVM操作。所以ReferenceQueue在这里作为JVM与上层Reference对象管理之间的消息传递方式

总结

  • 介绍了Reference具有的Acitive、Pending、Inactive、Enqueued四种状态,以及他们之间的转化。
  • 分析Reference的各个成员变量的作用。
  • 通过源码解读,描述ReferenceQueue入队过程,ReferenceQueue可以看做是JVM与Reference对象管理之间的桥梁
  • ReferenceHandler作为守护线程将pending状态的Reference节点加入到ReferenceQueue中(如果在Reference构造函数中指定了ReferenceQueue的话)。

下一篇文章会介绍Reference的各个子类:强引用、软引用、弱引用、虚引用。


PS:参考文献
http://www.importnew.com/26250.html
https://zhuanlan.zhihu.com/p/29254258

你可能感兴趣的:(深入理解Java中的引用(一)——Reference)