java byreference_深入理解Java中的引用(一)——Reference

深入理解Java中的引用(一)——Reference

本系列文章首先会介绍Reference类,为之后介绍的强引用、软引用、弱引用和虚引用打下基础。

最后会介绍虚引用在DirectBuffer回收中的应用。

引用(Reference)

在介绍四种不同类型的引用之前先看一下他们的父类:java.lang.ref.Reference。看以看到Reference 有五个成员变量:referent,queue,pending,next,discovered。下面会一一介绍各个成员变量的作用。

Reference状态

在介绍五个成员变量之前,首页要明确一点,Reference是有状态的,Reference对象的一共有四种状态。如下图所示

d275812816e5

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 wo = new WeakReference(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 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 discovered; /* used by VM */

上述discovered与pending的关系可以用下图表示

d275812816e5

image.png

5) 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节点被放在队列头,所以这是一个先进后出队列。 入队的示意图如下:

d275812816e5

image.png

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 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 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的各个子类:强引用、软引用、弱引用、虚引用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值