众所周知,java
中引用分为四种类型,分别为强引用(StrongReference
)、软引用(SoftReference
)、弱引用(WeakReference
)和虚引用(PhantomReference
)。四种引用的强度顺序是:
StrongReference > SoftReference > WeakReference > PhantomReference
其中 StrongReference
没有具体具现化的类, Object obj = new Object()
这个就属于强引用。
想弄懂这四种引用类型,就必须先了解 Reference
和 ReferenceQueue
这两个类。
一. Reference 类
1.1 重要属性
private T referent;
volatile ReferenceQueue<? super T> queue;
volatile Reference next;
transient private Reference<T> discovered;
private static Reference<Object> pending = null;
1.1.1 四种状态
想要了解这些属性的作用,先要知道对于一个 Reference
实例,它有四种状态:
-
Active
状态:表示这个Reference
是新创建状态,且它对应的引用对象referent
也没有被gc
回收。 -
Pending
状态:表示这个Reference
所对应的引用对象referent
已经被gc
回收了,等待将这个Reference
对象放到queue
队列中。
因此如果这个
Reference
对象创建的时候,没有设置queue
对象,那么就不会进入这个状态。
-
Enqueued
状态:表示这个Reference
对象已经被放到queue
队列中。
和
Pending
状态一样,如果创建Reference
对象时,没有设置queue
对象,那么也不会进入这个状态。
-
Inactive
状态:表示这个Reference
对象最终状态,无法再改变。
如果这个
Reference
对象没有设置queue
对象,那么当这个Reference
所对应的引用对象referent
被gc
回收后,就变成这个状态了。如果设置了queue
对象,就必须等到有人将这个Reference
对象从queue
队列中取出,才会进入这个状态。
所以我们可以看出,根据有没有设置 queue
对象,Reference
状态变化分为两种:
- 没有设置
queue
对象。当Reference
对象创建成功后,就是Active
状态,等到Reference
所对应的引用对象referent
被gc
回收了,那么就进入了Inactive
状态。 - 设置
queue
对象。当Reference
对象创建成功后,就是Active
状态,等到Reference
所对应的引用对象referent
被gc
回收了,就进入了Pending
状态,然后等待ReferenceHandler
线程将Reference
状态变成Enqueued
状态。最后等待用户将这个Reference
对象从queue
队列中取出,那么变成了Inactive
状态。
1.1.2 属性作用
先看一下 Reference
的构造函数
Reference(T referent) {
this(referent, null);
}
Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}
当我们创建 Reference
时,没有设置 queue
对象,那么默认的 queue
就是 ReferenceQueue.NULL
。
当我们了解了 Reference
四种状态的意义,那么就知道它的成员属性的作用了:
-
referent
:Reference
实例所对应的引用对象。 -
queue
: 用来储存Reference
实例的队列。 -
next
: 通过这个属性形成一个队列,用来记录这个Reference
对象的queue
对象中所有Enqueued
状态的对象。
active: NULL
pending: this
Enqueued: next reference in queue (or this if last)
Inactive: this
-
discovered
: 通过这个属性形成一个队列,用来记录所有处于Pending
状态的Reference
对象。 -
pending
: 这个是一个静态属性,所有Reference
对象共享的。
1.2 ReferenceHandler 内部类
1.2.1 ReferenceHandler 类
private static class ReferenceHandler extends Thread {
private static void ensureClassInitialized(Class<?> clazz) {
try {
Class.forName(clazz.getName(), true, clazz.getClassLoader());
} catch (ClassNotFoundException e) {
throw (Error) new NoClassDefFoundError(e.getMessage()).initCause(e);
}
}
static {
ensureClassInitialized(InterruptedException.class);
ensureClassInitialized(Cleaner.class);
}
ReferenceHandler(ThreadGroup g, String name) {
super(g, name);
}
public void run() {
while (true) {
tryHandlePending(true);
}
}
}
这个类的实现很简单,就不多介绍了。
1.2.2 静态代码块调用
public abstract class Reference<T> {
....
static {
ThreadGroup tg = Thread.currentThread().getThreadGroup();
for (ThreadGroup tgn = tg;
tgn != null;
tg = tgn, tgn = tg.getParent());
Thread handler = new Reference.ReferenceHandler(tg, "Reference Handler");
handler.setPriority(Thread.MAX_PRIORITY);
handler.setDaemon(true);
handler.start();
SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
@Override
public boolean tryHandlePendingReference() {
return tryHandlePending(false);
}
});
}
}
在 Reference
的静态代码块中,开启 ReferenceHandler
线程,名字叫 Reference Handler
。
所以当你用
jstack pid
查看java
的线程栈,你会看到一个名叫Reference Handler
线程.
1.2.3 tryHandlePending 方法
static boolean tryHandlePending(boolean waitForNotify) {
Reference<Object> r;
Cleaner c;
try {
// 防止并发冲突
synchronized (lock) {
// 如果静态变量有值,pending就是一个 pending状态的 Reference 对象,需要将它变成 `Enqueued` 状态
if (pending != null) {
r = pending;
c = r instanceof Cleaner ? (Cleaner) r : null;
// 设置下一个 `pending` 状态的 Reference 对象
pending = r.discovered;
r.discovered = null;
} else {
if (waitForNotify) {
// 如果没有,就等待,直到 gc 进行动作
lock.wait();
}
return waitForNotify;
}
}
} catch (OutOfMemoryError x) {
Thread.yield();
return true;
} catch (InterruptedException x) {
return true;
}
if (c != null) {
c.clean();
return true;
}
ReferenceQueue<? super Object> q = r.queue;
// 将 Reference 对象放入自己的 queue 队列中,变成 `Enqueued` 状态
if (q != ReferenceQueue.NULL) q.enqueue(r);
return true;
}
通过上面代码,我们直到 ReferenceHandler
类就是一个线程,它的作用就是将所有 Pending
状态的 Reference
对象,放入自己的 queue
队列中,变成 Enqueued
状态。
将
Reference
对象从Active
状态变成Pending
状态,是由gc
帮我们做的,这块代码我没有找到。
为什么需要 ReferenceHandler
这个线程,来将 Reference
对象从 Pending
状态变成 Enqueued
状态,而不是有 gc
程序一起做了呢?
我想可能是不想影响 gc
程序的效率,所以另开一个线程,来异步处理这些事情。
二. ReferenceQueue 类
2.1 重要属性
private static class Null<S> extends ReferenceQueue<S> {
boolean enqueue(Reference<? extends S> r) {
return false;
}
}
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;
属性分析:
-
Null
内部类:enqueue
方法返回false
,表示这个ReferenceQueue
队列不能存放Reference
对象。 -
NULL
和ENQUEUED
: 用于特殊标记。 -
head
:表示这个ReferenceQueue
队列的队列头。 -
queueLength
:表示这个ReferenceQueue
队列中存放的Reference
对象数量。
2.2 重要方法
2.2.1 enqueue
入队方法
boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */
synchronized (lock) {
ReferenceQueue<?> queue = r.queue;
// 如果 Reference对象的 queue 是 NULL 或者 ENQUEUED,
// 表示这个 Reference对象不能存放到queue队列中
if ((queue == NULL) || (queue == ENQUEUED)) {
return false;
}
// 如果 queue 不是当前 ReferenceQueue 对象,直接报错
assert queue == this;
// 将 r.queue 变成 ENQUEUED,表示这个Reference对象状态是Enqueued
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
对象的 queue
和 next
属性。
2.2.2 poll
出队方法
public Reference<? extends T> poll() {
if (head == null)
return null;
synchronized (lock) {
return reallyPoll();
}
}
private Reference<? extends T> reallyPoll() { /* Must hold lock */
Reference<? extends T> r = head;
if (r != null) {
@SuppressWarnings("unchecked")
Reference<? extends T> rn = r.next;
// head 指向队列中的下一个 Reference 对象
head = (rn == r) ? null : rn;
// 将 queue 设置成 null
r.queue = NULL;
// 将 next 设置成 自己
r.next = r;
queueLength--;
if (r instanceof FinalReference) {
sun.misc.VM.addFinalRefCount(-1);
}
return r;
}
return null;
}
从 ReferenceQueue
队列中获取一个 Reference
对象,这个 Reference
对象状态从 Enqueued
变成 Inactive
。
如果当前
ReferenceQueue
队列没有数据,那么返回null
。
2.2.2 remove
等待删除方法
public Reference<? extends T> remove() throws InterruptedException {
return remove(0);
}
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) {
long end = System.nanoTime();
timeout -= (end - start) / 1000_000;
if (timeout <= 0) return null;
start = end;
}
}
}
}
这个方法与 poll
方法比较,它们都能移除 ReferenceQueue
中的一个 Reference
对象,并且改变这个 Reference
对象状态。
当时 poll
方法的缺陷是你不知道 Reference
对象什么时候被加入到 ReferenceQueue
队列中。 而 remove
方法可以一直等待到 ReferenceQueue
队列中有数据。
三. PhantomReference、WeakReference 和 SoftReference
3.1 相关代码
public class PhantomReference<T> extends Reference<T> {
// 获取都是 0
public T get() {
return null;
}
public PhantomReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
public class WeakReference<T> extends Reference<T> {
public WeakReference(T referent) {
super(referent);
}
public WeakReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
public class SoftReference<T> extends Reference<T> {
static private long clock;
private long timestamp;
public SoftReference(T referent) {
super(referent);
this.timestamp = clock;
}
public SoftReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
this.timestamp = clock;
}
public T get() {
T o = super.get();
if (o != null && this.timestamp != clock)
this.timestamp = clock;
return o;
}
}
观察一下这三个类,我们发现:
-
WeakReference
和SoftReference
都有不设置ReferenceQueue
对象构造方法,但是PhantomReference
只有包含ReferenceQueue
对象的构造方法,说明创建PhantomReference
对象,必须设置对应的ReferenceQueue
对象。 -
PhantomReference
的get
方法直接返回null
,说明它获取不到所对应的引用对象。
3.2 示例代码
public static class User {
private String name;
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("User{");
sb.append("name='").append(name).append('\'');
sb.append('}');
return sb.toString();
}
}
public static void main(String[] agrs) throws InterruptedException {
User user = new User("zhang");
ReferenceQueue<User> queue = new ReferenceQueue<>();
Reference<User> reference = new PhantomReference<>(user, queue);
System.out.println("reference: "+ reference);
// 将引用对象设置成 null,可以让 gc 程序进行回收
user = null;
System.out.println("reference.get(): "+ reference.get());
System.out.println("reference.isEnqueued(): "+reference.isEnqueued());
System.out.println("queue.poll(): "+queue.poll());
// 进行 gc
System.gc();
System.out.println("===============gc 之后=================");
System.out.println("reference.get(): "+ reference.get());
System.out.println("reference.isEnqueued(): "+reference.isEnqueued());
System.out.println("queue.poll(): "+queue.poll());
}
运行结果:
reference: java.lang.ref.PhantomReference@60e53b93
reference.get(): null
reference.isEnqueued(): false
queue.poll(): null
===============gc 之后=================
reference.get(): null
reference.isEnqueued(): true
queue.poll(): java.lang.ref.PhantomReference@60e53b93
将 PhantomReference
换成 WeakReference
,运行结果:
reference: java.lang.ref.WeakReference@60e53b93
reference.get(): User{name='zhang'}
reference.isEnqueued(): false
queue.poll(): null
===============gc 之后=================
reference.get(): null
reference.isEnqueued(): true
queue.poll(): java.lang.ref.WeakReference@60e53b93
将 PhantomReference
换成 SoftReference
,运行结果:
reference: java.lang.ref.SoftReference@60e53b93
reference.get(): User{name='zhang'}
reference.isEnqueued(): false
queue.poll(): null
===============gc 之后=================
reference.get(): User{name='zhang'}
reference.isEnqueued(): false
queue.poll(): null
可以看出,对于 SoftReference
所引用的对象,即使调用 System.gc()
之后,不会将这个引用的对象回收。
四. 总结
从源码中我们可以得出,Reference
的主要作用有以下几点:
- 使用
Reference
包装一个引用对象referent
,如果这个引用对象referent
被gc
回收了,那么Reference
对象的get
方法获取的引用对象referent
就为null
。这一步是由gc
线程设置的。
注意,根据
Reference
类型不同,gc
线程何时回收Reference
所包装一个引用对象referent
也不同。PhantomReference
对象的get
方法返回一直为空。
- 如果给
Reference
设置一个ReferenceQueue
队列,那么当Reference
包装一个引用对象referent
被gc
回收时,可以从ReferenceQueue
队列中获取到所有referent
被回收的Reference
对象。起到监控作用。
网友评论