深入理解Java中的引用(一)——Reference
本系列文章首先会介绍Reference类,为之后介绍的强引用、软引用、弱引用和虚引用打下基础。
最后会介绍虚引用在DirectBuffer回收中的应用。
引用(Reference)
在介绍四种不同类型的引用之前先看一下他们的父类:java.lang.ref.Reference
。看以看到Reference 有五个成员变量:referent,queue,pending,next,discovered。下面会一一介绍各个成员变量的作用。
Reference状态
在介绍五个成员变量之前,首页要明确一点,Reference是有状态的,Reference对象的一共有四种状态。如下图所示
image.png
Reference对象所处状态不同,成员变量的值也会变化。
- Active: Reference对象新建时处于该状态。
- Pending: 当Reference包装的referent = null的时候,JVM会把Reference设置成pending状态。如果Reference创建时指定了ReferenceQueue,那么会被ReferenceHandler线程处理进入到ReferenceQueue队列中,如果没有就进入Inactive状态。
- Enqueue: 进入ReferenceQueue中的对象,等待被回收
- Inactive: Reference对象从ReferenceQueue取出来并被处理掉。处于Inactive的Reference对象状态不能再改变
核心成员变量
1)referent: 表示被包装的对象
下面代码中new Object()
就是被包装的对象。
WeakReference<Object> wo = new WeakReference<Object>(new Object());
2) queue: 表示被包装的对象被回收时,需要被通知的队列,该队列在Reference构造函数中指定。当referent被回收的时候,Reference对象就处在了Pending状态,Reference会被放入到该队列中,如果构造函数没有指定队列,那么就进入Inactive状态。
volatile ReferenceQueue<? super T> queue;
......
Reference(T referent) {
this(referent, null);
}
Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}
3)pending: 表示等待被加入到queue的Reference 列表。
/* 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理解链表有点费解,因为代码层面上看这明明就是Reference对象。其实当Reference处在Pending状态时,他的pending字段被赋值成了下一个要处理的对象(即下面讲的discovered),通过discovered可以拿到下一个对象并且赋值给pending,直到最后一个,所以这里就可以把它当成一个链表。而discovered是JVM的垃圾回收器添加进去的,大家可以不用关心底层细节。
4)discovered: 当处于Reference处在pending状态:discovered为pending集合中的下一个元素;其他状态:discovered为null
/* 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的关系可以用下图表示
image.png5) next: 当Reference对象在queue中时(即Reference处于Enqueued状态),next描述当前引用节点所存储的下一个即将被处理的节点。
/* When active: NULL
* pending: this
* Enqueued: next reference in queue (or this if last)
* Inactive: this
*/
@SuppressWarnings("rawtypes")
Reference next;
ReferenceHandler线程会把pending状态的Reference放入ReferenceQueue中,上面说的next,discovered 字段在入队之后也会发生变化,下一小节会介绍。
ReferenceQueue入队过程
上面说到ReferenceHandler线程会把pending状态的Reference对象放入到ReferenceQueue队列中。
查看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;
//设置queue状态
r.queue = ENQUEUED;
//改变next指针
r.next = (head == null) ? r : head;
head = r;
queueLength++;
if (r instanceof FinalReference) {
sun.misc.VM.addFinalRefCount(1);
}
lock.notifyAll();
return true;
}
}
可以看到入队的Reference节点r进入队列,Reference节点被放在队列头,所以这是一个先进后出队列。 入队的示意图如下:
ReferenceHandler线程
Reference类中另一个比较重要的成员是ReferenceHandler。ReferenceHandler是一个线程。当JVM加载Reference的时候,就会启动这个线程。用jstack查看该线程栈可以看到。Reference Handler
是JVM中的2号线程,并且线性优先级被设置为高优先级。
"Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007f7718075800 nid=0x10244 in Object.wait() [0x00007f7708363000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000d55b9580> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x00000000d55b9580> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
看源代码他是如何工作的:
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);
}
}
}
通过上面代码可以看到ReferenceHandler线程做的是不断的检查pending是否为null, 如果不为null,将pending对象进行入队操作,而pending的赋值由JVM操作。所以ReferenceQueue在这里作为JVM与上层Reference对象管理之间的消息传递方式。
总结
- 介绍了Reference具有的Acitive、Pending、Inactive、Enqueued四种状态,以及他们之间的转化。
- 分析Reference的各个成员变量的作用。
- 通过源码解读,描述ReferenceQueue入队过程,ReferenceQueue可以看做是JVM与Reference对象管理之间的桥梁,
- ReferenceHandler作为守护线程将pending状态的Reference节点加入到ReferenceQueue中(如果在Reference构造函数中指定了ReferenceQueue的话)。
下一篇文章会介绍Reference的各个子类:强引用、软引用、弱引用、虚引用。
PS:参考文献
http://www.importnew.com/26250.html
https://zhuanlan.zhihu.com/p/29254258
网友评论