ReferenceQueue、Reference详解

本文深入探讨Java中Reference和ReferenceQueue的工作原理,包括其状态变迁、内部结构及ReferenceHandler线程的作用。通过实例演示Reference从活跃到无效的全过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载自:http://www.jianshu.com/p/f86d3a43eec5
              http://www.cnblogs.com/jabnih/p/6580665.html

ReferenceQueue

       引用队列,在检测到适当的可到达性更改后,垃圾回收器将已注册的引用对象添加到该队列中,ReferenceQueue实现了入队(enqueue)和出队(poll),还有remove操作,内部元素head就是泛型的Reference:
    private volatile Reference<? extends T> head = null;
       ReferenceQueue的实现是由Reference自身的链表结构(单向循环链表)所实现的。所以说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;
            r.queue = ENQUEUED;
            r.next = (head == null) ? r : head;
            head = r;
            queueLength++;
            if (r instanceof FinalReference) {
                sun.misc.VM.addFinalRefCount(1);
            }
            lock.notifyAll();
            return true;
        }
    }
       从enqueue()代码可以看出来,queue仅存储当前的head节点,而后面的节点由每个reference节点通过自己的next来保持的。而且queue是一个后进先出的队列。当新的节点进入时成为了head,然后出队时也是先出的head。

Reference

         java.lang.ref.Reference是SoftReference、WeakReference、PhantomReference的基类。Reference对象是和垃圾回收密切配合实现,Reference的直接子类都是由JVM定制化处理的,因此在代码中直接继承于Reference类型没有任何作用。但可以继承JVM定制的Reference的子类。例如:Cleaner就继承了PhantomReference。

构造函数
       Reference内部提供2个构造函数,一个带queue,一个不带queue。
    Reference(T referent) {
        this(referent, null);
    }

    Reference(T referent, ReferenceQueue<? super T> queue) {
        this.referent = referent;
        this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
    }
       其中queue的意义在于可以在外部对这个queue进行监控。如果有对象即将被回收,那么相应的reference对象就会被放到这个queue里,我们就拿到reference再作一些事情。而如果不带的话,就只有不断地轮询reference对象,通过判断里面的get是否返回null,来判断是否被回收。phantomReference对象不能这样作,其get始终返回null,因此它只有带queue的构造函数。queue的默认值是ReferenceQueue.NULL。
       以上这两种构造方法均有相应的使用场景,取决于实际的应用。如WeakHashMap中就选择去查询queue的数据,来判定是否有对象将被回收。而ThreadLocalMap则采用判断get()是否为null来作处理。
       如果在创建一个引用对象时,指定了ReferenceQueue,那么当引用对象指向的对象达到合适的状态(根据引用类型不同而不同)时,GC会把引用对象本身添加到这个队列中,方便我们处理它,因为“引用对象指向的对象GC会自动清理,但是引用对象本身也是对象(是对象就占用一定资源),所以需要我们自己清理。”

Reference内部主要的成员
    private T referent;         /* Treated specially by GC */
    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;

    /* 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 */

    /* 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对象,而不仅仅是被回收的对象,会被放到queue里面,然后外部程序即可通过监控这个queue拿到相应的数据了。
         next即当前引用节点所存储的下一个即将被处理的节点。但next仅在放到queue中才会有意义,因为只有在enqueue的时候,会将next设置为下一个要处理的Reference对象。为了描述相应的状态值,在放到队列当中后,其queue就不会再引用这个队列了。而是引用一个特殊的ENQUEUED(内部定义的一个空队列)。因为已经放到队列当中,并且不会再次放到队列当中。
        discovered表示要处理的对象的下一个对象。即可以理解要处理的对象也是一个链表,通过discovered进行排队,这边只需要不停地拿到pending,然后再通过discovered不断地拿到下一个对象赋值给pending即可,直到取到了最有一个。它是被JVM使用的。
        pending是等待被入队的引用列表。JVM收集器会添加引用到这个列表,直到Reference-handler线程移除了它们。这个列表使用discovered字段来连接它下一个元素(即pending的下一个元素就是discovered对象。r = pending; pending = r.discovered)。

Reference状态值
       每个引用对象都有相应的状态描述,即描述自己以及其包装的对象当前处于一个什么样的状态,以方便进行查询,定位或处理。Reference有4种状态。四种状态用Reference的成员变量queue与next来标示。
       Active:活动状态,新创建的引用对象都是这个状态,即相应的对象为强引用状态。在这个状态下next == null,queue为构造Reference对象时传入的ReferenceQueue对象,默认是ReferenceQueue.NULL。在GC检测到引用对象的可达性发生变化之后,它的状态将变化为Pending或Inactive(如果引用对象在构造时指定了ReferenceQueue,那么转移到Pending;如果没指定,转移到Inactive)。
       Pending:准备放入queue当中的引用对象,在这个状态的对象将挨个地排队放到queue当中。在这个时间窗口期,相应的对象为Pending状态,都在pending列表中。此状态的下,next == this(由JVM设置),queue为定义时所引用的queue。这里需要注意的是,queue如果指定了,那么引用对象能转移到Pending,如果没指定,直接转移到Inactive,因为此时next == this,queue == ReferenceQueue.NULL。
       Enqueued:引用对象已经放到queue当中了。准备由外部线程来询循queue获取相应的数据。调用ReferenceQueue.enqueued()后的reference就会处于这个状态中。此状态中next 为该queue中的下一个引用,如果是该队列中的最后一个,那么为this,queue = ReferenceQueue.ENQUEUED。当reference实例从它的ReferenceQueue移除后,它将成为Inactive。没有注册queue的实例不会进入这个状态。
       Inactive:即此对象已经由外部从queue中获取到,并且已经处理掉了,此时next == this,queue == ReferenceQueue.NULL。即意味着此引用对象可以被回收,并且对内部封装的对象也可以被回收掉了。但实际的回收运行取决于clear()方法是否被调用(clear()方法会设置this.referent = null)。

Reference类加载后,就会启动Reference Handler线程
    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();
    }
       从源码可以看出,这个线程在Reference类的static构造块中启动,并且被设置为最高优先级MAX_PRIORITY和daemon状态,并随即启动该线程。
    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);
            }
        }
    }
       此线程要做的事情就是不断的检查pending是否为null,如果pending不为null,则将pending进行enqueue,否则线程进入wait状态。
       当Reference内部的referent对象的可达状态改变时,JVM会将该Reference对象放入pending链表,注意这时JVM会设置referent对象的next = this。并且这里enqueue的队列是在初始化Reference对象时传进来的queue,如果传入了null(实际使用的是ReferenceQueue.NULL),则ReferenceHandler则不进行enqueue操作,这时referent对象的状态已经是Inactive了。只有非RefernceQueue.NULL的queue才会将Reference进行enqueue。
          在enqueue()中,当reference实例的queue!=null && queue != ENQUEUED时;设置queue为ENQUEUED,next为下一个要处理的reference对象,或者若为最后一个则next==this。这时referent对象是Enqueue。
           当调用queue的remove或者poll方法时,就会将要处理的reference实例的queue设置为ReferenceQueue.NULL,next = this,此时reference实例就进入了Inactive状态,等待JVM回收。


具体执行例子:
// 创建一个引用队列
ReferenceQueue queue = new ReferenceQueue();

// 创建虚引用,此时状态为Active,并且Reference.pending为空,当前Reference.queue = 上面创建的queue,并且next=null
WeakReference reference = new WeakReference(new Object(), queue);
System.out.println(reference);
// 当GC执行后,由于是虚引用,所以回收该object对象,并且置于pending上,此时reference的状态为PENDING
System.gc();

/* ReferenceHandler从pending中取下该元素,并且将该元素放入到queue中,此时Reference状态为ENQUEUED,Reference.queue = ReferenceENQUEUED */

/* 当从queue里面取出该元素,则变为INACTIVE,Reference.queue = Reference.NULL */
Reference reference1 = queue.remove();
System.out.println(reference1);



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值