这是我个人对源码的理解,也希望大家批评指正。Reference主要是负责内存的一个状态,当然它还和java虚拟机,垃圾回收器打交道。Reference类首先把内存分为4种状态Active,Pending,Enqueued,Inactive,
1.一般来说内存一开始被分配的状态都是Active,
2.只有注册了队列的对象(构造的时候传了队列对象参数,即ReferenceQueue<? super T> queue;)才会处于这个状态,Pending大概是指快要被放进队列的对象,进入3。
3.只有注册了队列的对象才会处于这个状态,Enqueued就是对象的内存将要被回收,目前还没有回收我们已经把这个对象放入到一个队列中,方便我们查询某个对象是否将要被回收,如果某个对象在队列里,说明这个对象快要被垃圾回收器回收
4.Inactive就是最终的状态,不能再变为其它状态。说明对象已经不存在了
在内存分配的时候我么可以选择给它传入一个队列参数,这个参数的类名叫作ReferenceQueue,当然也可以选择不传入,取决于构造函数。
Reference类源码
/* -- Constructors -- */
Reference(T referent) {
this(referent, null);
}
Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}
Reference类里面有两个值得注意的成员变量
/* 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表示下一个要回收的对象,请注意,目前他不是正要回收的对象,正要回收的对象是后面要提到的pending 变量,并且这个是由垃圾回收器控制。
/* 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;
pending这个变量表示它是下一个要进入ReferenceQueue队列的对象,并且它也是目前正要回收的对象。
我们从Reference源码中发现没有给discovered和pending 赋值的地方,也就是说pending和discovered很有可能是垃圾回收器给它们赋值。
这个队列ReferenceQueue到底有什么作用呢
查看ReferenceQueue的源码,发现这个类里面主要的的方法也就是2个
enqueue这个方法其实比较简单,首先是用lock这个锁实现同步,也就是在某个时间段只能有一个线程访问,后面的也就是把对象插入进去,新插进的元素在最上面,也就是头部。
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;
}
}
reallyPoll这个函数主要是从队列里面取出元素,表面上说是队列,个人感觉它就像栈,
出队
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;
}
ReferenceQueue这个类有什么用呢,它跟Reference有什么关系呢,关系主要体现在这几个方面,首先Reference这个类里面在构造函数的时候有两种选择,一种是给它传入一个ReferenceQueue,一种是不传,如果不传的话,等这个对象的内存被回收了,直接从Active变为Inactive状态,如果我们传入了ReferenceQueue,那么当对象的内存回收的时候会经历一个过程,从Active->Pending->Enqueued->Inactive。pending状态就是等待着进入ReferenceQueue队列的这样一个状态,说白了它目前还没被回收,只是对象的引用(用户代码中的引用)被移除了,pending保存了这个引用,回收的过程中,ReferenceHandler这个线程会把该对象的引用(pending)放入到我们在构造函数时传入的那个队列里面,总结一下,当经历第三个状态的时候,和ReferenceQueue的关系很密切,
Reference类里面的源码,这部分源码的意思大概是开启一个后台线程,并且这个线程的优先级很高,一直负责循环回收内存,只要有内存可以回收,就马上回收,没有的话就 阻塞等待,但是值得注意的是ReferenceQueue<Object> q = r.queue;if (q != ReferenceQueue.NULL) q.enqueue(r); 这段代码,也就是说如果被回收对象里面的成员queue不为空,当它被回收时,我们就把它放入到队列里面,进入到前面提到的第三个状态,这个过程能发生,要有个条件,就是前面提到的,你必须在构造该对象时传一个队列参数给这个对象得成员queue。我们把这个对象放到队列里有什么用,当我们在用软引用的时候,可以去查询这个队列,来知道对象内存是不是快要被回收了。
Reference类源码 ReferenceHandler 是Reference的一个内部类
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);
}
}
}
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();
}