Java 学习:Reference 和 ReferenceQueue 类

Reference 类

对象的引用句柄,主要是负责内存的一个状态,当然它还和 java 虚拟机,垃圾回收器打交道。

referent 指代 reference 引用的对象,如果 referent 已经被程序或者垃圾回收器清理,则为 null。它直接被 GC 程序处理。

private T referent;         /* Treated specially by GC */

引用实例处于四种可能的内部状态之一:

Active:
reference 如果处于此状态,会受到垃圾处理器的特殊处理。当垃圾回收器检测到 referent 已经更改为合适的状态后(没有任何强引用和软引用关联),会在某个时间将实例的状态更改为 Pending 或者 Inactive。具体取决于实例是否在创建时注册到一个引用队列中。在前一种情况下(将状态更改为 Pending),他还会将实例添加到 pending-Reference 列表中。新创建的实例处于活动状态。

Pending:
实例如果处于此状态,表明它是 pending-Reference 列表中的一个元素,等待被 Reference-handler 线程做入队处理。未注册引用队列的实例永远不会处于该状态。

Enqueued:
实例如果处于此状态,表明它已经是它注册的引用队列中的一个元素,当它被从引用队列中移除时,它的状态将会变为 Inactive,未注册引用队列的实例永远不会处于该状态。

Inactive:
实例如果处于此状态,那么它就是个废实例了(滑稽),它的状态将永远不会再改变了。

所以实例一共有四种状态,Active(活跃状态)、Pending(准备回收状态)、Enqueued(已回收状态)、Inactive(最终状态)。当然,Pending和Enqueued状态是引用实例在创建时注册了引用队列才会有。

volatile ReferenceQueue<? super T> queue;

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

实例处于不同的状态,queue、next 变量对应不同的值,JVM中并没有显示定义这样的状态,而是通过next和queue来进行判断。

Active:如果创建 Reference 对象时,没有传入 ReferenceQueue,queue=ReferenceQueue.NULL。如果有传入,则 queue 指向传入的 ReferenceQueue 队列对象。next == null;

Pending:queue 为初始化时传入 ReferenceQueue 对象;next == this;

Enqueue:queue == ReferenceQueue.ENQUEUED;next 为 queue 中下一个 reference 对象,或者若为最后一个了 next == this;

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

/* 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 表示要处理的对象的下一个对象。即可以理解要处理的对象也是一个链表,通过 discovered 进行排队,这边只需要不停地拿到 pending,然后再通过 discovered 不断地拿到下一个对象赋值给 pending 即可,直到取到了最有一个。它是被 JVM 使用的。

锁对象用于与垃圾收集器同步。收集器必须在每个收集周期的开始获得此锁。用作在操作 pending 链表时的同步对象。

static private class Lock { }
private static Lock lock = new Lock();

用来保存那些需要被放入队列中的 reference,收集器会把引用添加到这个列表里来,Reference-handler 线程会从中移除它们。这个列表由上面的 lock 对象锁进行保护。列表使用 discovered 字段来链接它的元素。

private static Reference<Object> pending = null;

pending:等待添加到 queue 中的元素链表。注意这是一个静态对象,意味着所有 Reference 对象共用同一个 pending 队列。

ReferenceQueue queue = new ReferenceQueue();
// 创建弱引用,此时状态为Active,并且Reference.pending为空,当前Reference.queue = 上面创建的queue,并且next=null
WeakReference wf1 = new WeakReference(new Object(), queue);
WeakReference wf2 = new WeakReference(new Object(), queue);
WeakReference wf3 = new WeakReference(new Object(), queue);
//手动触发回收
System.gc();

  1. 准备回收的 reference 对象会被 JVM 存放到 pending 链表,内部会启动一个 ReferenceHandler 线程把 pending 链表元素存放到引用队列中。
  2. 而 从上图中发现 discovered 链表存放的是下一个需要回收的对象链表。

由此可知,加入到回收的动作流程:垃圾回收器会把 References 添加进入,Reference-handler thread 会移除它。

ReferenceHandler线程

Reference 类中有一个特殊的线程叫 ReferenceHandler,专门处理那些 pending 链表中的引用对象。ReferenceHandler 类是 Reference 类的一个静态内部类,继承自 Thread,所以这条线程就叫它 ReferenceHandler 线程。

private static class ReferenceHandler extends Thread {

    private static void ensureClassInitialized(Class<?> clazz) {
        //预先加载指定Class实例
        try {
            Class.forName(clazz.getName(), true, clazz.getClassLoader());
        } catch (ClassNotFoundException e) {
            throw (Error) new NoClassDefFoundError(e.getMessage()).initCause(e);
        }
    }

    static {
        // 预加载并初始化 InterruptedException 和 Cleaner 类
        // 来避免出现在循环运行过程中时由于内存不足而无法加载它们       
        ensureClassInitialized(InterruptedException.class);
        ensureClassInitialized(Cleaner.class);
    }

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

    public void run() {
        while (true) {
            tryHandlePending(true);
        }
    }
}

使用一个死循环来对 pending 链表执行操作:

static boolean tryHandlePending(boolean waitForNotify) {
    Reference<Object> r;
    Cleaner c;
    try {
        synchronized (lock) {
            if (pending != null) {
                r = pending;
                // 使用 'instanceof' 有时会导致OOM
                // 所以在将r从链表中摘除时先进行这个操作
                c = r instanceof Cleaner ? (Cleaner) r : null;
                // 移除头结点,将pending指向其后一个节点
                pending = r.discovered;
                //从链表中移除
                r.discovered = null;
            } else {
                // 在锁上等待可能会造成OOM,因为它会试图分配exception对象
                if (waitForNotify) {
                    // 导致当前线程等待,直到另一个线程调用此对象的notify()方法或notifyAll()方法,或指定的时间已过
                    lock.wait();
                }
                // 重试
                return waitForNotify;
            }
        }
    } catch (OutOfMemoryError x) {
        Thread.yield();
        // 重试
        return true;
    } catch (InterruptedException x) {
        // 重试
        return true;
    }

    // 如果移除的元素是Cleaner类型,则执行其clean方法
    if (c != null) {
        c.clean();
        return true;
    }

    ReferenceQueue<? super Object> q = r.queue;
    //对Pending状态的实例入队操作
    if (q != ReferenceQueue.NULL) q.enqueue(r);
    return true;
}

在根线程组中启动了一条最高优先级的 ReferenceHandler 线程,并覆盖了 JVM 中对 pending 的默认处理方式。

static {
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        for (ThreadGroup tgn = tg;
        tgn != null;
        tg = tgn, tgn = tg.getParent());
        Thread handler = new ReferenceHandler(tg, "Reference Handler");
        // 将handler线程注册到根线程组中并设置最高优先级
        handler.setPriority(Thread.MAX_PRIORITY);
        handler.setDaemon(true);
        handler.start();

        // 覆盖jvm的默认处理方式
        SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
            @Override
            public boolean tryHandlePendingReference() {
                    return tryHandlePending(false);
            }
        });
}

ReferenceQueue

引用队列,在检测到适当的可到达性更改后,垃圾回收器将已注册的引用对象添加到队列中,ReferenceQueue 实现了入队(enqueue)和出队(poll),还有 remove 操作,内部元素 head 就是泛型的 Reference。

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;
  1. 使用 ENQUEUED -> Enqueue、NULL -> Inactive 表示对应的状态
  2. 使用 head 链表保存 Inactive 状态的 Reference 对象

从上图可以看出,最终指定的引用队列的 head 链表存放了最终状态的 Reference 对象。

接下来看下引入队列的入队操作:

boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */
    synchronized (lock) {
        // queue 为 null 或者 queue 已经被回收了,直接返回
        ReferenceQueue<?> queue = r.queue;
        if ((queue == NULL) || (queue == ENQUEUED)) {
            return false;
        }
        assert queue == this;
        // 将queue设置为Enqueued,表示已经被回收
        r.queue = ENQUEUED;
        // 判断当前链表是否为null,不为null,r.next指向head,为null则指向自己
        r.next = (head == null) ? r : head;
        // head指针指向r
        head = r;
        queueLength++;
        if (r instanceof FinalReference) {
            sun.misc.VM.addFinalRefCount(1);
        }
        lock.notifyAll();
        return true;
    }
}

 从上面的源码分析可以看出:JVM 负责设置可回收的对象引用 Reference 的内部状态及断开与被引用对象的关系,而 Reference 内部线程会不断轮询处理可回收的引用 Reference,把可回收的引用存放到引用队列,接着 JVM 垃圾回收机制回收引用队列的元素。


整体流程:

参考文章:

java 源码系列 - 带你读懂 Reference 和 ReferenceQueue

你不可不知的Java引用类型之——Reference源码解析

猜你喜欢

转载自blog.csdn.net/dilixinxixitong2009/article/details/88315025