基于jdk1.8进行分析的。
ReferenceQueue
Reference queues,在适当的时候检测到对象的可达性发生改变后,垃圾回收器就将已注册的引用对象添加到此队列中。在一个对象被垃圾回收器扫描到将要进行回收时,其相应的引用包装类,即reference对象会被放入其注册的引用队列queue中。可以从queue中获取到相应的对象信息,同时进行额外的处理。比如反向操作,数据清理,资源释放等。
成员属性
//出队标识
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;
成员方法
enqueue(Reference<? extends T> r)
// 这个方法仅会被Reference类调用
boolean enqueue(Reference<? extends T> r) {
synchronized (lock) {
// 检测从获取这个锁之后,该Reference没有入队,并且没有被移除
ReferenceQueue<?> queue = r.queue;
if ((queue == NULL) || (queue == ENQUEUED)) {
return false;
}
assert queue == this;
// 将reference的queue标记为ENQUEUED
r.queue = ENQUEUED;
// 将r设置为链表的头结点
r.next = (head == null) ? r : head;
head = r;
queueLength++;
// 如果r的FinalReference类型,则将FinalRef+1
if (r instanceof FinalReference) {
sun.misc.VM.addFinalRefCount(1);
}
lock.notifyAll();
return true;
}
}
poll()
是入队的方法,使用了lock对象锁进行同步,将传入的r添加到队列中,并重置头结点为传入的节点。
public Reference<? extends T> poll() {
if (head == null)
return null;
synchronized (lock) {
return reallyPoll();
}
}
@SuppressWarnings("unchecked")
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;
}
poll方法将头结点弹出。
remove()
/**
* 移除并返回队列首节点,此方法将阻塞到获取到一个Reference对象或者超时才会返回
* timeout时间的单位是毫秒
*/
public Reference<? extends T> remove(long timeout)
throws IllegalArgumentException, InterruptedException{
if (timeout < 0) {
throw new IllegalArgumentException("Negative timeout value");
}
synchronized (lock) {
Reference<? extends T> r = reallyPoll();
if (r != null) return r;
long start = (timeout == 0) ? 0 : System.nanoTime();
// 死循环,直到取到数据或者超时
for (;;) {
lock.wait(timeout);
r = reallyPoll();
if (r != null) return r;
if (timeout != 0) {
// System.nanoTime方法返回的是纳秒,1毫秒=1纳秒*1000*1000
long end = System.nanoTime();
timeout -= (end - start) / 1000_000;
if (timeout <= 0) return null;
start = end;
}
}
}
}
/**
* 移除并返回队列首节点,此方法将阻塞到获取到一个Reference对象才会返回
*/
public Reference<? extends T> remove() throws InterruptedException {
return remove(0);
}
这里两个方法都是从队列中移除首节点,与poll不同的是,它会阻塞到超时或者取到一个Reference对象才会返回。
ReferenceQueue一般用来与SoftReference、WeakReference或者PhantomReference配合使用,将需要关注的引用对象注册到引用队列后,便可以通过监控该队列来判断关注的对象是否被回收,从而执行相应的方法。
Reference类
这是引用对象的抽象基类,这个类中定义了所有引用对象的常用操作。由于引用对象是通过与垃圾回收器密切合作来实现的,因此,不能直接为此类创建子类。
通过源码注释内容,我们可以看到一共有四种状态,分别如下:
* 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.
- active:Active状态的Reference会受到GC的特别关注,当GC察觉到引用的可达性变化为其它的状态之后,它的状态将变化为Pending或Inactive,到底转化为Pending状态还是Inactive状态取决于此Reference对象创建时是否注册了queue.如果注册了queue,则将添加此实例到pending-Reference list中。 新创建的Reference实例的状态是Active。
- Pending:在pending-Reference list中等待着被Reference-handler 线程入队列queue中的元素就处于这个状态。没有注册queue的实例是永远不可能到达这一状态。
- Enqueued:当实例被移动到ReferenceQueue中时,Reference的状态为Inactive。没有注册ReferenceQueue的不可能到达这一状态的。
- Inactive:一旦一个实例变为Inactive,则这个状态永远都不会再被改变。
简单总结下就是:新创建的Reference实例就是Active的,进入pending队列就是就是pending状态,通过Reference-Handler线程放入queue队列中,那么就是Enqueued状态,当对象出队之后,那么就会变为inactive状态。
成员属性
//被GC特殊对待
private T referent;
//这个queue是通过构造函数传入的,表示创建一个Reference时,要将其注册到那个queue上。
volatile ReferenceQueue<? super T> queue;
//指向下一个,通过下面源码中的注释内容,可以看到next的取值
@SuppressWarnings("rawtypes")
/* When active: NULL
* pending: this
* Enqueued: next reference in queue (or this if last)
* Inactive: this
*/
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;
//可以简单理解为需要同步操作上锁
static private class Lock { };
private static Lock lock = new Lock();
//pending队列
private static Reference<Object> pending = null;
构造方法
Reference(T referent) {
this(referent, null);
}
Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}
构造方法一共有两个,可见一个指定了队列,一个没有指定队列。
下面整体看一下关于队列以及Reference状态间的关系
* 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.
代码注释内容如上。我们一个个往下看。
Active
- queue = ReferenceQueue with which instance is registered, or ReferenceQueue.NULL if it was not registered with a queue; next = null.
简单理解过来就是指定的queue是什么以及next值是什么。
queue应该为:如果创建该对象的的时候指定了队列,那就使用指定的队列,如果没有那么是ReferenceQueue的NULL。而此时next的值为null。
关于queue可以通过上面分析的构造方法可以看出。
Pending
- queue = ReferenceQueue with which instance is registered;next = this
同样给出了queue应该是什么,next的值是什么。
Enqueued
- queue = ReferenceQueue.ENQUEUED; next = Following instance in queue, or this if at end of list.
通过上面的ReferenceQueue源码分析可以知道ENQUEUED
static ReferenceQueue<Object> ENQUEUED = new Null<>();
进入队列的时候,Reference状态为Enqueued。下面是入队调用的方法。
public boolean enqueue() {
return this.queue.enqueue(this);
}
可以看出是调用的是通过调用ReferenceQueue的enqueue方法实现的。
r.queue = ENQUEUED;
r.next = (head == null) ? r : head;
给出了queue以及next的取值。
inActive
queue = ReferenceQueue.NULL; next = this.
通过ReferenceQueue方法的poll()可以看到对应的取值。通过上面源码分析我们可以知道当对象出队列的时候状态改为inActive。而在ReferenceQueue实现中,poll方法实际是通过reallyPoll方法实现的。里面标识了queue的取值以及next的取值。
r.queue = NULL;
r.next = r;
以上就是针对四种状态的分析。
在Reference中,还有一个静态内部类ReferenceHandler。下面我们看一下
ReferenceHandler
废话不多说,直接贴源码。
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构造块中启动,并且被设置为最高优先级和daemon状态。此线程要做的事情就是不断的的检查pending是否为null,如果pending不为null,则将pending进行enqueue,否则线程进行wait状态。下面是线程的具体实现。
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);
}
}
}
从上面可以看出来,是处理Reference的线程,所以不用多说,关注方法run。
拓展部分
了解过或者有JVM基础的朋友往下看,如果没有,可能会有些吃力。
引用类型一共可以分为四种。分别是:StrongReference,WeakReference,PhantomReference,WeakReference四种。
StrongReference
这个引用在Java中没有相应的类与之对应,但是强引用比较普遍,例如:Object obj = new Object();这里的obj就是要给强引用,如果一个对象具有强引用,则垃圾回收器始终不会回收此对象。当内存不足时,JVM会抛出OOM异常使程序异常终止,但也不会靠回收强引用的对象来解决内存不足的问题。
SoftReference
如果一个对象只有软引用,则在内存充足的情况下是不会回收此对象的,但是,在内部不足即将要抛出OOM异常时就会回收此对象来解决内存不足的问题。
private static ReferenceQueue<Object> rq = new ReferenceQueue<Object>();
Object obj = new Object();
SoftReference<Object> sf = new SoftReference(obj,rq);
WeakReference
WeakReference基本与SoftReference类似,只是回收的策略不同。只要GC发现一个对象只有弱引用,则就会回收此弱引用对象。但是由于GC所在的线程优先级比较低,不会立即发现所有弱引用对象并进行回收。只要GC对它所管辖的内存区域进行扫描时发现了弱引用对象就进行回收。
上面的解释可以从gc角度这么理解。首先,我们知道可以作为GC Roots的对象包括:
-
虚拟机栈中引用的对象
-
方法区中类静态属性引用的对象
-
方法区中常量引用的对象
-
本地方法栈JNI引用的对象
往往到达一个对象的引用链会存在多条,那么怎么判断可达性呢?垃圾回收时会依据两个原则来判断对象的可达性:
-
单一路径中,以最弱的引用为准
-
多路径中,以最强的引用为准
这么解释来看是不是就清晰一些了。如果还不清楚,我们看下面的例子:
public class TestWeakReference {
private static ReferenceQueue<Object> rq = new ReferenceQueue<Object>();
public static void main(String[] args) {
Object obj = new Object();
WeakReference<Object> wr = new WeakReference(obj,rq);
System.out.println(wr.get()!=null);
obj = null;
System.gc();
System.out.println(wr.get()!=null);//false,这是因为WeakReference被回收
}
}
可以看到obj有两种引用链,两种引用类型,一种是强引用,也就是new出来的,另外一个是weak的,也就是通过get获得的,当执行了obj=null语句后,那么强引用消失了,此时只存在一条弱引用,那么此时就会被回收。
PhantomReference
PhantomReference,即虚引用,虚引用并不会影响对象的生命周期。虚引用的作用为:跟踪垃圾回收器收集对象这一活动的情况。当GC一旦发现了虚引用对象,则会将PhantomReference对象插入ReferenceQueue队列,而此时PhantomReference对象并没有被垃圾回收器回收,而是要等到ReferenceQueue被你真正的处理后才会被回收。
注意:PhantomReference必须要和ReferenceQueue联合使用,SoftReference和WeakReference可以选择和ReferenceQueue联合使用也可以不选择,这使他们的区别之一。
FinalReference
关于最后Reference的子类中,还有FinalReference这个类。但是通过源码可以看到并没有什么操作。而其子类Finalizer通过源码可以看到是有一些动作的。如果读者想了解为什么实现了finalize方法,gc第一次不会回收,也就是对象可以复活以及为什么gc只会调用一次该方法等,可以自行拓展,此处不再一一赘述。
以上就是针对此次源码整个分析的过程,如果有不对的地方,还请指正。