美文网首页Java基础知识Java
深入理解Java中的引用(一)——Reference

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

作者: 樂浩beyond | 来源:发表于2018-07-01 16:55 被阅读15次

    深入理解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<Object> wo = new WeakReference<Object>(new Object());
    

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

    volatile ReferenceQueue<? super T> queue;
    ......
        Reference(T referent) {
            this(referent, null);
        }
    
        Reference(T referent, ReferenceQueue<? super T> 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<Object> 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<T> 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<? extends T> 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<Object> 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<Object> 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

        本文链接:https://www.haomeiwen.com/subject/jlfhuftx.html