ReferenceQueue、Reference及其子类源码分析

版权声明:https://blog.csdn.net/Burgess_Lee https://blog.csdn.net/Burgess_Lee/article/details/89311210

基于jdk1.8进行分析的。

ReferenceQueue

Reference queues,在适当的时候检测到对象的可达性发生改变后,垃圾回收器就将已注册的引用对象添加到此队列中。在一个对象被垃圾回收器扫描到将要进行回收时,其相应的引用包装类,即reference对象会被放入其注册的引用队列queue中。可以从queue中获取到相应的对象信息,同时进行额外的处理。比如反向操作,数据清理,资源释放等。

成员属性

    //出队标识
    static ReferenceQueue<Object> NULL = new Null<>();
    //出队标识
    static ReferenceQueue<Object> ENQUEUED = new Null<>();
    //锁对象
    static private class Lock { };
    private Lock lock = new Lock();
    //链表的头结点
    private volatile Reference<? extends T> head = null;
    //队列的大小
    private long queueLength = 0;

成员方法

enqueue(Reference<? extends T> r)

// 这个方法仅会被Reference类调用
boolean enqueue(Reference<? extends T> r) { 
    synchronized (lock) {
        // 检测从获取这个锁之后,该Reference没有入队,并且没有被移除
        ReferenceQueue<?> queue = r.queue;
        if ((queue == NULL) || (queue == ENQUEUED)) {
            return false;
        }
        assert queue == this;
        // 将reference的queue标记为ENQUEUED
        r.queue = ENQUEUED;
        // 将r设置为链表的头结点
        r.next = (head == null) ? r : head;
        head = r;
        queueLength++;
        // 如果r的FinalReference类型,则将FinalRef+1
        if (r instanceof FinalReference) {
            sun.misc.VM.addFinalRefCount(1);
        }
        lock.notifyAll();
        return true;
    }
}

poll()

是入队的方法,使用了lock对象锁进行同步,将传入的r添加到队列中,并重置头结点为传入的节点。 

    public Reference<? extends T> poll() {
        if (head == null)
            return null;
        synchronized (lock) {
            return reallyPoll();
        }
    }
    @SuppressWarnings("unchecked")
    private Reference<? extends T> reallyPoll() {       /* Must hold lock */
        Reference<? extends T> r = head;
        if (r != null) {
            head = (r.next == r) ?
                null :
                r.next; // Unchecked due to the next field having a raw type in Reference
            r.queue = NULL;
            r.next = r;
            queueLength--;
            if (r instanceof FinalReference) {
                sun.misc.VM.addFinalRefCount(-1);
            }
            return r;
        }
        return null;
    }

poll方法将头结点弹出。

remove()

/**
  * 移除并返回队列首节点,此方法将阻塞到获取到一个Reference对象或者超时才会返回
  * timeout时间的单位是毫秒
  */
public Reference<? extends T> remove(long timeout)
    throws IllegalArgumentException, InterruptedException{
    if (timeout < 0) {
        throw new IllegalArgumentException("Negative timeout value");
    }
    synchronized (lock) {
        Reference<? extends T> r = reallyPoll();
        if (r != null) return r;
        long start = (timeout == 0) ? 0 : System.nanoTime();
        // 死循环,直到取到数据或者超时
        for (;;) {
            lock.wait(timeout);
            r = reallyPoll();
            if (r != null) return r;
            if (timeout != 0) {
                // System.nanoTime方法返回的是纳秒,1毫秒=1纳秒*1000*1000
                long end = System.nanoTime();
                timeout -= (end - start) / 1000_000;
                if (timeout <= 0) return null;
                start = end;
            }
        }
    }
}

/**
 * 移除并返回队列首节点,此方法将阻塞到获取到一个Reference对象才会返回
 */
public Reference<? extends T> remove() throws InterruptedException {
    return remove(0);
}

这里两个方法都是从队列中移除首节点,与poll不同的是,它会阻塞到超时或者取到一个Reference对象才会返回。

ReferenceQueue一般用来与SoftReference、WeakReference或者PhantomReference配合使用,将需要关注的引用对象注册到引用队列后,便可以通过监控该队列来判断关注的对象是否被回收,从而执行相应的方法。

Reference类

这是引用对象的抽象基类,这个类中定义了所有引用对象的常用操作。由于引用对象是通过与垃圾回收器密切合作来实现的,因此,不能直接为此类创建子类。

通过源码注释内容,我们可以看到一共有四种状态,分别如下:

     *     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.
  • active:Active状态的Reference会受到GC的特别关注,当GC察觉到引用的可达性变化为其它的状态之后,它的状态将变化为Pending或Inactive,到底转化为Pending状态还是Inactive状态取决于此Reference对象创建时是否注册了queue.如果注册了queue,则将添加此实例到pending-Reference list中。 新创建的Reference实例的状态是Active。
  • Pending:在pending-Reference list中等待着被Reference-handler 线程入队列queue中的元素就处于这个状态。没有注册queue的实例是永远不可能到达这一状态。
  • Enqueued:当实例被移动到ReferenceQueue中时,Reference的状态为Inactive。没有注册ReferenceQueue的不可能到达这一状态的。
  • Inactive:一旦一个实例变为Inactive,则这个状态永远都不会再被改变。

简单总结下就是:新创建的Reference实例就是Active的,进入pending队列就是就是pending状态,通过Reference-Handler线程放入queue队列中,那么就是Enqueued状态,当对象出队之后,那么就会变为inactive状态。

成员属性

    //被GC特殊对待
    private T referent;        
    //这个queue是通过构造函数传入的,表示创建一个Reference时,要将其注册到那个queue上。
    volatile ReferenceQueue<? super T> queue;
    //指向下一个,通过下面源码中的注释内容,可以看到next的取值
    @SuppressWarnings("rawtypes")
    /* When active:   NULL
     *     pending:   this
     *    Enqueued:   next reference in queue (or this if last)
     *    Inactive:   this
     */
    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;

    //可以简单理解为需要同步操作上锁
    static private class Lock { };
    private static Lock lock = new Lock();
    //pending队列
    private static Reference<Object> pending = null;

构造方法

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

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

构造方法一共有两个,可见一个指定了队列,一个没有指定队列。

下面整体看一下关于队列以及Reference状态间的关系

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

代码注释内容如上。我们一个个往下看。

Active

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

简单理解过来就是指定的queue是什么以及next值是什么。

queue应该为:如果创建该对象的的时候指定了队列,那就使用指定的队列,如果没有那么是ReferenceQueue的NULL。而此时next的值为null。

关于queue可以通过上面分析的构造方法可以看出。

Pending

  •  queue = ReferenceQueue with which instance is registered;next = this

同样给出了queue应该是什么,next的值是什么。

Enqueued

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

通过上面的ReferenceQueue源码分析可以知道ENQUEUED

static ReferenceQueue<Object> ENQUEUED = new Null<>();

进入队列的时候,Reference状态为Enqueued。下面是入队调用的方法。 

    public boolean enqueue() {
        return this.queue.enqueue(this);
    }

可以看出是调用的是通过调用ReferenceQueue的enqueue方法实现的。

r.queue = ENQUEUED;
r.next = (head == null) ? r : head;

给出了queue以及next的取值。

inActive

queue = ReferenceQueue.NULL; next = this.

通过ReferenceQueue方法的poll()可以看到对应的取值。通过上面源码分析我们可以知道当对象出队列的时候状态改为inActive。而在ReferenceQueue实现中,poll方法实际是通过reallyPoll方法实现的。里面标识了queue的取值以及next的取值。

r.queue = NULL;
r.next = r;

以上就是针对四种状态的分析。

在Reference中,还有一个静态内部类ReferenceHandler。下面我们看一下

ReferenceHandler

废话不多说,直接贴源码。

    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构造块中启动,并且被设置为最高优先级和daemon状态。此线程要做的事情就是不断的的检查pending是否为null,如果pending不为null,则将pending进行enqueue,否则线程进行wait状态。下面是线程的具体实现。

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);
            }
        }
    }

从上面可以看出来,是处理Reference的线程,所以不用多说,关注方法run。

拓展部分

了解过或者有JVM基础的朋友往下看,如果没有,可能会有些吃力。

引用类型一共可以分为四种。分别是:StrongReference,WeakReference,PhantomReference,WeakReference四种。

StrongReference

这个引用在Java中没有相应的类与之对应,但是强引用比较普遍,例如:Object obj = new Object();这里的obj就是要给强引用,如果一个对象具有强引用,则垃圾回收器始终不会回收此对象。当内存不足时,JVM会抛出OOM异常使程序异常终止,但也不会靠回收强引用的对象来解决内存不足的问题。

SoftReference

如果一个对象只有软引用,则在内存充足的情况下是不会回收此对象的,但是,在内部不足即将要抛出OOM异常时就会回收此对象来解决内存不足的问题。

private static ReferenceQueue<Object> rq = new ReferenceQueue<Object>();
Object obj = new Object();
SoftReference<Object> sf = new SoftReference(obj,rq);

WeakReference

WeakReference基本与SoftReference类似,只是回收的策略不同。只要GC发现一个对象只有弱引用,则就会回收此弱引用对象。但是由于GC所在的线程优先级比较低,不会立即发现所有弱引用对象并进行回收。只要GC对它所管辖的内存区域进行扫描时发现了弱引用对象就进行回收。

上面的解释可以从gc角度这么理解。首先,我们知道可以作为GC Roots的对象包括:

  • 虚拟机栈中引用的对象

  • 方法区中类静态属性引用的对象

  • 方法区中常量引用的对象

  • 本地方法栈JNI引用的对象

往往到达一个对象的引用链会存在多条,那么怎么判断可达性呢?垃圾回收时会依据两个原则来判断对象的可达性:

  • 单一路径中,以最弱的引用为准

  • 多路径中,以最强的引用为准

这么解释来看是不是就清晰一些了。如果还不清楚,我们看下面的例子:

    public class TestWeakReference {
        private static ReferenceQueue<Object> rq = new ReferenceQueue<Object>();
        public static void main(String[] args) {
            Object obj = new Object();
            WeakReference<Object> wr = new WeakReference(obj,rq);
            System.out.println(wr.get()!=null);
            obj = null;
            System.gc();
            System.out.println(wr.get()!=null);//false,这是因为WeakReference被回收
        }

    }

可以看到obj有两种引用链,两种引用类型,一种是强引用,也就是new出来的,另外一个是weak的,也就是通过get获得的,当执行了obj=null语句后,那么强引用消失了,此时只存在一条弱引用,那么此时就会被回收。

PhantomReference

PhantomReference,即虚引用,虚引用并不会影响对象的生命周期。虚引用的作用为:跟踪垃圾回收器收集对象这一活动的情况。当GC一旦发现了虚引用对象,则会将PhantomReference对象插入ReferenceQueue队列,而此时PhantomReference对象并没有被垃圾回收器回收,而是要等到ReferenceQueue被你真正的处理后才会被回收。

注意:PhantomReference必须要和ReferenceQueue联合使用,SoftReference和WeakReference可以选择和ReferenceQueue联合使用也可以不选择,这使他们的区别之一。

FinalReference

关于最后Reference的子类中,还有FinalReference这个类。但是通过源码可以看到并没有什么操作。而其子类Finalizer通过源码可以看到是有一些动作的。如果读者想了解为什么实现了finalize方法,gc第一次不会回收,也就是对象可以复活以及为什么gc只会调用一次该方法等,可以自行拓展,此处不再一一赘述。

以上就是针对此次源码整个分析的过程,如果有不对的地方,还请指正。

猜你喜欢

转载自blog.csdn.net/Burgess_Lee/article/details/89311210