转载自:http://www.jianshu.com/p/f86d3a43eec5
http://www.cnblogs.com/jabnih/p/6580665.html
以上这两种构造方法均有相应的使用场景,取决于实际的应用。如WeakHashMap中就选择去查询queue的数据,来判定是否有对象将被回收。而ThreadLocalMap则采用判断get()是否为null来作处理。
如果在创建一个引用对象时,指定了ReferenceQueue,那么当引用对象指向的对象达到合适的状态(根据引用类型不同而不同)时,GC会把引用对象本身添加到这个队列中,方便我们处理它,因为“引用对象指向的对象GC会自动清理,但是引用对象本身也是对象(是对象就占用一定资源),所以需要我们自己清理。”
queue是对象即将被回收时所要通知的队列。当对象即将被回收时,整个reference对象,而不仅仅是被回收的对象,会被放到queue里面,然后外部程序即可通过监控这个queue拿到相应的数据了。
next即当前引用节点所存储的下一个即将被处理的节点。但next仅在放到queue中才会有意义,因为只有在enqueue的时候,会将next设置为下一个要处理的Reference对象。为了描述相应的状态值,在放到队列当中后,其queue就不会再引用这个队列了。而是引用一个特殊的ENQUEUED(内部定义的一个空队列)。因为已经放到队列当中,并且不会再次放到队列当中。
discovered表示要处理的对象的下一个对象。即可以理解要处理的对象也是一个链表,通过discovered进行排队,这边只需要不停地拿到pending,然后再通过discovered不断地拿到下一个对象赋值给pending即可,直到取到了最有一个。它是被JVM使用的。
pending是等待被入队的引用列表。JVM收集器会添加引用到这个列表,直到Reference-handler线程移除了它们。这个列表使用discovered字段来连接它下一个元素(即pending的下一个元素就是discovered对象。r = pending; pending = r.discovered)。
Active:活动状态,新创建的引用对象都是这个状态,即相应的对象为强引用状态。在这个状态下next == null,queue为构造Reference对象时传入的ReferenceQueue对象,默认是ReferenceQueue.NULL。在GC检测到引用对象的可达性发生变化之后,它的状态将变化为Pending或Inactive(如果引用对象在构造时指定了ReferenceQueue,那么转移到Pending;如果没指定,转移到Inactive)。
Pending:准备放入queue当中的引用对象,在这个状态的对象将挨个地排队放到queue当中。在这个时间窗口期,相应的对象为Pending状态,都在pending列表中。此状态的下,next == this(由JVM设置),queue为定义时所引用的queue。这里需要注意的是,queue如果指定了,那么引用对象能转移到Pending,如果没指定,直接转移到Inactive,因为此时next == this,queue == ReferenceQueue.NULL。
Enqueued:引用对象已经放到queue当中了。准备由外部线程来询循queue获取相应的数据。调用ReferenceQueue.enqueued()后的reference就会处于这个状态中。此状态中next 为该queue中的下一个引用,如果是该队列中的最后一个,那么为this,queue = ReferenceQueue.ENQUEUED。当reference实例从它的ReferenceQueue移除后,它将成为Inactive。没有注册queue的实例不会进入这个状态。
Inactive:即此对象已经由外部从queue中获取到,并且已经处理掉了,此时next == this,queue == ReferenceQueue.NULL。即意味着此引用对象可以被回收,并且对内部封装的对象也可以被回收掉了。但实际的回收运行取决于clear()方法是否被调用(clear()方法会设置this.referent = null)。
当Reference内部的referent对象的可达状态改变时,JVM会将该Reference对象放入pending链表,注意这时JVM会设置referent对象的next = this。并且这里enqueue的队列是在初始化Reference对象时传进来的queue,如果传入了null(实际使用的是ReferenceQueue.NULL),则ReferenceHandler则不进行enqueue操作,这时referent对象的状态已经是Inactive了。只有非RefernceQueue.NULL的queue才会将Reference进行enqueue。
在enqueue()中,当reference实例的queue!=null && queue != ENQUEUED时;设置queue为ENQUEUED,next为下一个要处理的reference对象,或者若为最后一个则next==this。这时referent对象是Enqueue。
当调用queue的remove或者poll方法时,就会将要处理的reference实例的queue设置为ReferenceQueue.NULL,next = this,此时reference实例就进入了Inactive状态,等待JVM回收。
具体执行例子:
http://www.cnblogs.com/jabnih/p/6580665.html
ReferenceQueue
引用队列,在检测到适当的可到达性更改后,垃圾回收器将已注册的引用对象添加到该队列中,ReferenceQueue实现了入队(enqueue)和出队(poll),还有remove操作,内部元素head就是泛型的Reference: private volatile Reference<? extends T> head = null;
ReferenceQueue的实现是由Reference自身的链表结构(单向循环链表)所实现的。所以说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;
}
}
从enqueue()代码可以看出来,queue仅存储当前的head节点,而后面的节点由每个reference节点通过自己的next来保持的。而且queue是一个后进先出的队列。当新的节点进入时成为了head,然后出队时也是先出的head。Reference
java.lang.ref.Reference是SoftReference、WeakReference、PhantomReference的基类。Reference对象是和垃圾回收密切配合实现,Reference的直接子类都是由JVM定制化处理的,因此在代码中直接继承于Reference类型没有任何作用。但可以继承JVM定制的Reference的子类。例如:Cleaner就继承了PhantomReference。构造函数
Reference内部提供2个构造函数,一个带queue,一个不带queue。 Reference(T referent) {
this(referent, null);
}
Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}
其中queue的意义在于可以在外部对这个queue进行监控。如果有对象即将被回收,那么相应的reference对象就会被放到这个queue里,我们就拿到reference再作一些事情。而如果不带的话,就只有不断地轮询reference对象,通过判断里面的get是否返回null,来判断是否被回收。phantomReference对象不能这样作,其get始终返回null,因此它只有带queue的构造函数。queue的默认值是ReferenceQueue.NULL。以上这两种构造方法均有相应的使用场景,取决于实际的应用。如WeakHashMap中就选择去查询queue的数据,来判定是否有对象将被回收。而ThreadLocalMap则采用判断get()是否为null来作处理。
如果在创建一个引用对象时,指定了ReferenceQueue,那么当引用对象指向的对象达到合适的状态(根据引用类型不同而不同)时,GC会把引用对象本身添加到这个队列中,方便我们处理它,因为“引用对象指向的对象GC会自动清理,但是引用对象本身也是对象(是对象就占用一定资源),所以需要我们自己清理。”
Reference内部主要的成员
private T referent; /* Treated specially by GC */
volatile ReferenceQueue<? super T> queue;
/* When active: NULL
* pending: this
* Enqueued: next reference in queue (or this if last)
* Inactive: this
*/
@SuppressWarnings("rawtypes")
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; /* used by VM */
/* 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;
referent表示其引用的对象,即在构造的时候需要被包装在其中的对象。queue是对象即将被回收时所要通知的队列。当对象即将被回收时,整个reference对象,而不仅仅是被回收的对象,会被放到queue里面,然后外部程序即可通过监控这个queue拿到相应的数据了。
next即当前引用节点所存储的下一个即将被处理的节点。但next仅在放到queue中才会有意义,因为只有在enqueue的时候,会将next设置为下一个要处理的Reference对象。为了描述相应的状态值,在放到队列当中后,其queue就不会再引用这个队列了。而是引用一个特殊的ENQUEUED(内部定义的一个空队列)。因为已经放到队列当中,并且不会再次放到队列当中。
discovered表示要处理的对象的下一个对象。即可以理解要处理的对象也是一个链表,通过discovered进行排队,这边只需要不停地拿到pending,然后再通过discovered不断地拿到下一个对象赋值给pending即可,直到取到了最有一个。它是被JVM使用的。
pending是等待被入队的引用列表。JVM收集器会添加引用到这个列表,直到Reference-handler线程移除了它们。这个列表使用discovered字段来连接它下一个元素(即pending的下一个元素就是discovered对象。r = pending; pending = r.discovered)。
Reference状态值
每个引用对象都有相应的状态描述,即描述自己以及其包装的对象当前处于一个什么样的状态,以方便进行查询,定位或处理。Reference有4种状态。四种状态用Reference的成员变量queue与next来标示。Active:活动状态,新创建的引用对象都是这个状态,即相应的对象为强引用状态。在这个状态下next == null,queue为构造Reference对象时传入的ReferenceQueue对象,默认是ReferenceQueue.NULL。在GC检测到引用对象的可达性发生变化之后,它的状态将变化为Pending或Inactive(如果引用对象在构造时指定了ReferenceQueue,那么转移到Pending;如果没指定,转移到Inactive)。
Pending:准备放入queue当中的引用对象,在这个状态的对象将挨个地排队放到queue当中。在这个时间窗口期,相应的对象为Pending状态,都在pending列表中。此状态的下,next == this(由JVM设置),queue为定义时所引用的queue。这里需要注意的是,queue如果指定了,那么引用对象能转移到Pending,如果没指定,直接转移到Inactive,因为此时next == this,queue == ReferenceQueue.NULL。
Enqueued:引用对象已经放到queue当中了。准备由外部线程来询循queue获取相应的数据。调用ReferenceQueue.enqueued()后的reference就会处于这个状态中。此状态中next 为该queue中的下一个引用,如果是该队列中的最后一个,那么为this,queue = ReferenceQueue.ENQUEUED。当reference实例从它的ReferenceQueue移除后,它将成为Inactive。没有注册queue的实例不会进入这个状态。
Inactive:即此对象已经由外部从queue中获取到,并且已经处理掉了,此时next == this,queue == ReferenceQueue.NULL。即意味着此引用对象可以被回收,并且对内部封装的对象也可以被回收掉了。但实际的回收运行取决于clear()方法是否被调用(clear()方法会设置this.referent = null)。
Reference类加载后,就会启动Reference Handler线程
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构造块中启动,并且被设置为最高优先级MAX_PRIORITY和daemon状态,并随即启动该线程。 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);
}
}
}
此线程要做的事情就是不断的检查pending是否为null,如果pending不为null,则将pending进行enqueue,否则线程进入wait状态。当Reference内部的referent对象的可达状态改变时,JVM会将该Reference对象放入pending链表,注意这时JVM会设置referent对象的next = this。并且这里enqueue的队列是在初始化Reference对象时传进来的queue,如果传入了null(实际使用的是ReferenceQueue.NULL),则ReferenceHandler则不进行enqueue操作,这时referent对象的状态已经是Inactive了。只有非RefernceQueue.NULL的queue才会将Reference进行enqueue。
在enqueue()中,当reference实例的queue!=null && queue != ENQUEUED时;设置queue为ENQUEUED,next为下一个要处理的reference对象,或者若为最后一个则next==this。这时referent对象是Enqueue。
当调用queue的remove或者poll方法时,就会将要处理的reference实例的queue设置为ReferenceQueue.NULL,next = this,此时reference实例就进入了Inactive状态,等待JVM回收。
具体执行例子:
// 创建一个引用队列
ReferenceQueue queue = new ReferenceQueue();
// 创建虚引用,此时状态为Active,并且Reference.pending为空,当前Reference.queue = 上面创建的queue,并且next=null
WeakReference reference = new WeakReference(new Object(), queue);
System.out.println(reference);
// 当GC执行后,由于是虚引用,所以回收该object对象,并且置于pending上,此时reference的状态为PENDING
System.gc();
/* ReferenceHandler从pending中取下该元素,并且将该元素放入到queue中,此时Reference状态为ENQUEUED,Reference.queue = ReferenceENQUEUED */
/* 当从queue里面取出该元素,则变为INACTIVE,Reference.queue = Reference.NULL */
Reference reference1 = queue.remove();
System.out.println(reference1);