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