java源码分析---Reference、ReferenceQueue

Reference、ReferenceQueue源码解析

在分析WeakHashMap源码的过程中我们接触到了Reference、ReferenceQueue两个类,以前自己只是知道这些类的存在,在看WeakHashMap源码之前并不知道他们的用途,今天就对着两个类进行分析
1. Reference
为什么需要Reference对象?
我们知道在我们的程序运行的时候,如果一个gc 想要回收一个对象,必须保证该对象没有任何引用指向此对象,否则gc不能成功回收该对象.如果我们既想拥有该对象,又想让垃圾回收器回收对象的时候能够回收该对象,那就需要使用Reference对象,Reference 对象可以看做是对我们对象的包装,可以通过 Reference.set()方法把对象”包裹进去”,当需要使用的时候通过 Reference.get()方法获取对象,当垃圾回收器进行垃圾回收时候,垃圾回收器能够根据不同类型的 Reference 派生类来决定是否回收 Reference 内包装的对象
1.1 Reference体系结构
Java还引入了SoftReference,WeakReference,PhantomReference,FinalReference ,这些类放在java.lang.ref包下,类的继承体系如下图
这里写图片描述

/**
 * Abstract base class for reference objects.  This class defines the
 * operations common to all reference objects.  Because reference objects are
 * implemented in close cooperation with the garbage collector, this class may
 * not be subclassed directly.
 *
 * @author   Mark Reinhold
 * @since    1.2
 */
public abstract class Reference<T>

首先我们看下Reference类前面的一段注释,翻译过来:这是引用对象的抽象基类,这个类中定义了所有引用对象的常用操作。由于引用对象是通过与垃圾回收器密切合作来实现的,因此,不能直接为此类创建子类。
以上就是源码中对此类的一个说明,我们可能获得到的有用信息为:Reference类是基类且和GC是密切相关的。
1.2 Reference属性

// 用于保存对象的引用,GC会根据不同Reference来特别对待
private T referent;
// 如果需要通知机制,则保存的对对应的队列
ReferenceQueue<? super T> queue;
/* 这个用于实现一个单向循环链表,用以将保存需要由ReferenceHandler处理的引用 */
Reference next;

static private class Lock { };
// 锁,用于同步pending队列的进队和出队
private static Lock lock = new Lock();
// 此属性保存一个PENDING的队列,配合上述next一起使用
private static Reference pending = null;

1.3 Reference构造方法

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

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

Reference构造方法有两个,最终是使用的Reference(T referent, ReferenceQueue

static private class Lock { }

1.4.2 ReferenceHandler

private static class ReferenceHandler extends Thread {

        private static void ensureClassInitialized(Class<?> clazz) {
            try {
                Class.forName(clazz.getName(), true, clazz.getClassLoader());
            } catch (ClassNotFoundException e) {
                throw (Error) new NoClassDefFoundError(e.getMessage()).initCause(e);
            }
        }

        static {
            // pre-load and initialize InterruptedException and Cleaner classes
            // so that we don't get into trouble later in the run loop if there's
            // memory shortage while loading/initializing them lazily.
            ensureClassInitialized(InterruptedException.class);
            ensureClassInitialized(Cleaner.class);
        }

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

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

通过源码我们了解到ReferenceHandler 继承了Thread,首先判断了Class是否已加载,然后调用线程的构造方法创建线程,其run方法中使用了tryHandlePending方法,我们看下源码:

static boolean tryHandlePending(boolean waitForNotify) {
        Reference<Object> r;
        Cleaner c;
        try {
            synchronized (lock) {
                if (pending != null) {
                    r = pending;
                    // 'instanceof' might throw OutOfMemoryError sometimes
                    // so do this before un-linking 'r' from the 'pending' chain...
                    c = r instanceof Cleaner ? (Cleaner) r : null;
                    // unlink 'r' from 'pending' chain
                    pending = r.discovered;
                    r.discovered = null;
                } else {
                    // The waiting on the lock may cause an OutOfMemoryError
                    // because it may try to allocate exception objects.
                    if (waitForNotify) {
                        lock.wait();
                    }
                    // retry if waited
                    return waitForNotify;
                }
            }
        } catch (OutOfMemoryError x) {
            // Give other threads CPU time so they hopefully drop some live references
            // and GC reclaims some space.
            // Also prevent CPU intensive spinning in case 'r instanceof Cleaner' above
            // persistently throws OOME for some time...
            Thread.yield();
            // retry
            return true;
        } catch (InterruptedException x) {
            // retry
            return true;
        }

        // Fast path for cleaners
        if (c != null) {
            c.clean();
            return true;
        }

        ReferenceQueue<? super Object> q = r.queue;
        if (q != ReferenceQueue.NULL) q.enqueue(r);
        return true;
    }

从源码中可以看出,这个线程在Reference类的static构造块中启动,并且被设置为最高优先级和daemon状态。此线程要做的事情就是不断的的检查pending是否为null,如果pending不为null,则将pending进行enqueue,否则线程进行wait状态
1.5 Reference四个子类
强引用:FinalReference
强引用指的是,程序中有直接可达的引用,而不需要通过任何引用对象,如Object obj = new Object();中,obj为强引用。
软引用:SoftReference
软引用,非强引用,但是可以通过软引用对象来访问。软引用的对象,只有在内存不足的时候(抛出OOM异常前),垃圾收集器会决定回收该软引用所指向的对象。软引用通常用于实现内存敏感的缓存。

SoftReference<Object> softRef = new SoftReference<Object>(new Object());

弱引用:WeakReference
弱引用,非强引用和软引用,但是可以通过弱引用对象来访问。弱引用的对象,不管内存是否足够,只要被垃圾收集器发现,该引用的对象就会被回收。实际的应用见WeakHashMap等。

WeakReference<Object> weakRef = new WeakReference<Object>(new Object());

虚引用:PhantomReference
虚引用,该引用必须和引用队列(ReferenceQueue)一起使用,一般用于实现追踪垃圾收集器的回收动作,比如在对象被回收的时候,会调用该对象的finalize方法,在使用虚引用可以实现该动作,也更加安全。

Object obj = new Object();
ReferenceQueue<Object> refQueue = new ReferenceQueue<>();
PhantomReference<Object> phantom = new PhantomReference<Object>(obj, refQueue);

1.6 Reference四种状态
在Reference类的开始有一段注释,如下:

 /* A Reference instance is in one of four possible internal states:
     *
     *     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.
     *
     * The state is encoded in the queue and next fields as follows:
     *
     *     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.
     *
     * With this scheme the collector need only examine the next field in order
     * to determine whether a Reference instance requires special treatment: If
     * the next field is null then the instance is active; if it is non-null,
     * then the collector should treat the instance normally.
     *
     * To ensure that a concurrent collector can discover active Reference
     * objects without interfering with application threads that may apply
     * the enqueue() method to those objects, collectors should link
     * discovered objects through the discovered field. The discovered
     * field is also used for linking Reference objects in the pending list.
     */

这段注释主要说明了Reference有四种状态,对应的状态转移图如下:
这里写图片描述
1.6.1 Active
新构造出来的Reference实例都处于这样一个状态
1.6.2 Pending
Pending状态的Reference实例的queue = ReferenceQueue with which instance is registered;这个我们都比较好理解
1.6.3 Enqueued
而为什么Enqueued状态的Reference实例的queue为ReferenceQueue.ENQUEUED以及next的值,我们是可以从ReferenceQueue中分析得到的?
分析如下:从上面的介绍可知,当Reference实例入队列queue时,Reference状态就变为了Enqueued,而从ReferenceQueue的enqueue方法的源码中有这样两行代码r.queue = ENQUEUED;r.next = (head == null) ? r : head;,r.queue = ENQUEUED实现了对于入队的Reference的queue都进行了入队标识;这行代码r.next = (head == null) ? r : head;实现入队的Reference加在了链表的head位置
1.6.4 Inactive
而为什么Inactive状态的Reference实例的queue为ReferenceQueue.NULL以及next = this,我们是可以从ReferenceQueue中分析得到的?
分析如下:从上面的介绍可知,当Reference实例出队列queue时,Reference状态就变为了Inactive,而从ReferenceQueue的poll方法的源码中有这样两行代码r.queue = NULL;r.next = r;,r.queue = NULL实现了对于出队的Reference的queue都进行了出队标识;这行代码r.next = r;实现出队的Reference对象的next指向自己
1.7 Reference总结
由于pending是由JVM来赋值的,当Reference内部的referent对象的可达状态发生改变时,JVM会将Reference对象放入到pending链表中。因此,在例子中的代码o = null这一句,它使得o对象满足垃圾回收的条件,并且在后边显示调用了System.gc(),垃圾收集进行的时候会标记WeakReference的referent的对象o为不可达(使得wr.get()==null),并且通过赋值给pending,触发ReferenceHandler线程来处理pending.ReferenceHandler线程要做的是将pending对象enqueue。但在这个程序中我们从构造函数传入的为null,即实际使用的是ReferenceQueue.NULL,ReferenceHandler线程判断queue如果为ReferenceQueue.NULL则不进行enqueue,如果不是,则进行enqueue操作。
ReferenceQueue.NULL相当于我们提供了一个空的Queue去监听垃圾回收器给我们的反馈,并且对这种反馈不做任何处理。要处理反馈,则必须要提供一个非ReferenceQueue.NULL的queue。WeakHashMap类中提供的就是一个由意义的ReferenceQueue,非ReferenceQueue.NULL。
Active:新创建的引用实例处于Active状态,但当GC检测到该实例引用的实际对象的可达性发生某些适当的改变(实际对象对于GC roots不可达)后,它的状态将会根据此实例是否注册在引用队列中而变成Pending或是Inactive。
Pending:当引用实例被放置在pending-Reference list中时,它处于Pending状态。此时,该实例在等待一个叫Reference-handler的线程将此实例进行enqueue操作。如果某个引用实例没有注册在一个引用队列中,该实例将永远不会进入Pending状态。
Enqueued: 当引用实例被添加到它注册在的引用队列中时,该实例处于Enqueued状态。当某个引用实例被从引用队列中删除后,该实例将从Enqueued状态变为Inactive状态。如果某个引用实例没有注册在一个引用队列中,该实例将永远不会进入Enqueued状态。
Inactive:一旦某个引用实例处于Inactive状态,它的状态将不再会发生改变,同时说明该引用实例所指向的实际对象一定会被GC所回收。
事实上Reference类并没有显示地定义内部状态值,JVM仅需要通过成员queue和next的值就可以判断当前引用实例处于哪个状态:
Active:queue为创建引用实例时传入的ReferenceQueue的实例或是ReferenceQueue.NULL;next为null
Pending:queue为创建引用实例时传入的ReferenceQueue的实例;next为this
Enqueued:queue为ReferenceQueue.ENQUEUED;next为队列中下一个需要被处理的实例或是this如果该实例为队列中的最后一个
Inactive:queue为ReferenceQueue.NULL;next为this
2. ReferenceQueue

/**
 * Reference queues, to which registered reference objects are appended by the
 * garbage collector after the appropriate reachability changes are detected.
 *
 * @author   Mark Reinhold
 * @since    1.2
 */

public class ReferenceQueue<T>

引用队列,在检测到适当的可到达性更改后,垃圾回收器将已注册的引用对象添加到该队列中
ReferenceQueue实现了队列的入队(enqueue)和出队(poll),其中的内部元素就是我们上文中提到的Reference对象。队列元素的存储结构是单链式存储,依靠每个reference对象的next域去找下一个元素。
主要成员有:

private  volatile Reference extends T> head = null;

用来存储当前需要被处理的节点

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

static变量NUlL和ENQUEUED分别用来表示没有提供默认引用队列的空队列和已经执行enqueue操作的队列。
常用方法:
enqueue(Reference

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

简单来说,入队操作就是将每次需要入队的引用实例放在头节点的位置,并将它的next域指向旧的头节点元素。因此整个ReferenceQueue是一个后进先出的数据结构
poll()方法,源码如下:

    public Reference<? extends T> poll() {
        if (head == null)
            return null;
        synchronized (lock) {
            return reallyPoll();
        }
    }

实现思路也相当简单,就是判断队列中是否为空,如果不为空,则取出链表中head位置的元素即可,出队的Reference对象要加上出队标识NULL

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值