Java引用类型之:Reference源码解析
1 简介
Reference是所有引用类型的父类,定义了引用的公共行为和操作,
2 Reference类结构
imageReference类与垃圾回收是密切配合的,所以该类不能被直接子类化。简单来讲,Reference的继承类都是经过严格设计的,甚至连成员变量的先后顺序都不能改变,所以在代码中直接继承Reference类是没有任何意义的。但是可以继承Reference类的子类。
例如:Finalizer 继承自 FinalReference,Cleaner 继承自 PhantomReference
3 核心属性
3.1 referent引用
referent对象内部存在引用(referent)用来保存引用对象的地址。
public abstract class Reference<T>{
//...省略代码
/**
* referent引用
*/
private T referent;
//...省略代码
}
3.2 pending队列
reference对象内部维护着一个pending单项链表队列,当referenc引用对象被判断可以回收时JVM会将reference对象放入此队列。这里需要注意pending是静态变量,意味着是所有Reference对象共享的。所有referenc对象都会放入同一个队列中。
public abstract class Reference<T>{
//...省略代码
/**
* pending单项链表队列(就pending表示head指针)
*/
private static Reference<Object> pending = null;
/**
* discovered作为pending队列中每一个Reference指向下一个的引用
*/
transient private Reference<T> discovered;
}
image
3.3 referenceQueue队列
reference对象内部维护着一个单项链表队列,当referenc引用对象被判断可以回收时,reference对象内部ReferenceHandler线程会将 reference对象从pending队列取出放入referenceQueue这个队列中。通过监控这个队列,取出这个reference对象,就可以对reference对象进行一些善后处理。
实现referenceQueue上是一个单项链表结构,其head指针在ReferenceQueue内部,reference对象内部存在一个属性next将链表节点串联起来
public abstract class Reference<T>{
//...省略代码
/**
* referenceQueue单项链表队列(head指针在ReferenceQueue内部)
*/
volatile ReferenceQueue<? super T> queue;
/**
* next作为queue队列中每一个Reference指向下一个的引用
*/
@SuppressWarnings("rawtypes")
Reference next;
//...省略代码
}
public class ReferenceQueue<T> {
/** 标识Reference对象状态(从ReferenceQueue移除) **/
static ReferenceQueue<Object> NULL = new Null<>();
/** 标识Reference对象状态(加入ReferenceQueue队列) **/
static ReferenceQueue<Object> ENQUEUED = new Null<>();
/**
* 链表头部引用
*/
private volatile Reference<? extends T> head = null;
/**
* 链表节点长度
*/
private long queueLength = 0;
//...省略代码
}
image
入队操作
**
* 这个方法仅会被Reference中ReferenceHandler线程调用
* 将Reference加入ReferenceQueue队列中
*/
boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */
synchronized (lock) {
/**
* 如果Reference对象没有设置queue,或者已经加入ReferenceQueue队列中 (queue == ENQUEUED)
* 直接返回false
* **/
ReferenceQueue<?> queue = r.queue;
if ((queue == NULL) || (queue == ENQUEUED)) {
return false;
}
assert queue == this;
/** 标识Reference对象加入ReferenceQueue队列 **/
r.queue = ENQUEUED;
/** 将Reference加入ReferenceQueue队列中 **/
r.next = (head == null) ? r : head;
head = r;
/** 队列数量+1 **/
queueLength++;
/** 如果Reference对象为FinalReference 引用数量+1 **/
if (r instanceof FinalReference) {
sun.misc.VM.addFinalRefCount(1);
}
lock.notifyAll();
return true;
}
}
出队操作
/**
* 从队列头部弹出节点
*/
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;
/** 将Reference从ReferenceQueue队列中取出 **/
if (r != null) {
/** 获取ReferenceQueue队列head之后Reference对象 **/
head = (r.next == r) ?
null :
r.next;
/** 标识Reference对象从ReferenceQueue队列中被取出 **/
r.queue = NULL;
r.next = r;
/** 队列数量+1 **/
queueLength--;
/** 如果Reference对象为FinalReference 引用数量+1 **/
if (r instanceof FinalReference) {
sun.misc.VM.addFinalRefCount(-1);
}
/** 返回 **/
return r;
}
return null;
}
删除操作
/**
* 删除队列元素
*/
public Reference<? extends T> remove() throws InterruptedException {
return remove(0);
}
/**
* 移除并返回队列首节点,此方法将阻塞到获取到一个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) {
long end = System.nanoTime();
timeout -= (end - start) / 1000_000;
if (timeout <= 0) return null;
start = end;
}
}
}
}
应用场景
ReferenceQueue一般用来与SoftReference、WeakReference或者PhantomReference配合使用,将需要关注的引用对象注册到引用队列后,便可以通过监控该队列来判断关注的对象是否被回收,从而执行相应的方法。
1 使用引用队列进行数据监控什么时候回收
当我们使用有限的堆空间,不断创建大的对象,并将大对象被弱引用唯一指向,由于是弱可达对象在内存不足时gc都会清理,我们通过一个线程不断监听ReferenceQueue中数据,感知哪些对象被回收
//-verbose:gc -Xms4m -Xmx4m -Xmn2m
public class ReferenceQueueTest {
private static ReferenceQueue<byte[]> rq = new ReferenceQueue<>();
private static int _1M = 1024 * 1024;
public static void main(String[] args) {
Object value = new Object();
Map<WeakReference<byte[]>, Object> map = new HashMap<>();
Thread thread = new Thread(ReferenceQueueTest::run);
thread.setDaemon(true);
thread.start();
for(int i = 0;i < 100;i++) {
byte[] bytes = new byte[_1M];
WeakReference<byte[]> weakReference = new WeakReference<>(bytes, rq);
map.put(weakReference, value);
bytes=null;
}
System.out.println("map.size->" + map.size());
int aliveNum = 0;
for (Map.Entry<WeakReference<byte[]>, Object> entry : map.entrySet()){
if (entry != null){
if (entry.getKey().get() != null){
aliveNum++;
}
}
}
System.out.println("total" + aliveNum);
}
private static void run() {
try {
int n = 0;
WeakReference k;
while ((k = (WeakReference) rq.remove()) != null) {
System.out.println((++n) + "clear:" + k);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2 队列监控的反向操作
反向操作,即意味着一个数据变化了,可以通过Reference对象反向拿到相关的数据,从而进行后续的处理。下面有个小栗子:
public class ReferenceQueueTest2 {
private static ReferenceQueue<byte[]> referenceQueue = new ReferenceQueue<>();
private static int _1M = 1024 * 1024;
public static void main(String[] args) throws InterruptedException {
final Map<Object, MyWeakReference> hashMap = new HashMap<>();
Thread thread = new Thread(() -> {
try {
int n = 0;
MyWeakReference k;
while(null != (k = (MyWeakReference) referenceQueue.remove())) {
System.out.println((++n) + "回收了:" + k);
//反向获取,移除对应的entry
hashMap.remove(k.key);
//额外对key对象作其它处理,比如关闭流,通知操作等
}
} catch(InterruptedException e) {
e.printStackTrace();
}
});
thread.setDaemon(true);
thread.start();
for(int i = 0;i < 10000;i++) {
byte[] bytesKey = new byte[_1M];
byte[] bytesValue = new byte[_1M];
hashMap.put(bytesKey, new MyWeakReference(bytesKey, bytesValue, referenceQueue));
}
}
static class MyWeakReference extends WeakReference<byte[]> {
private Object key;
MyWeakReference(Object key, byte[] referent, ReferenceQueue<? super byte[]> q) {
super(referent, q);
this.key = key;
}
}
}
如果没有这个队列,就只能通过不断地轮询reference对象,通过get方法是否返回null( phantomReference对象不能这样做,其get方法始终返回null,因此它只有带queue的构造函数 )来判断对象是否被回收。
4 reference生命周期
reference引用对象一共有四种状态,Active(活跃状态)、Pending(半死不活状态)、Enqueued(濒死状态)、Inactive(凉凉状态),
reference引用对象状态和reference对象状态相互关联,我们可以通过感知reference对象来判断reference指针对象正处于何种状态。
-
Active(活跃状态):表示reference引用对象处于活跃正常状态,垃圾回收器会监视这个reference引用对象。当reference引用对象没有任何强引用关联时将会发生改变。
-
Pending(半死不活状态):当reference引用对象没有被任何强引用且其reference对象设置了referenceQueue,JVM将reference对象添加到Pending队列,Pending队列是一个中间队列,用来存放将要放入ReferenceQueue队列的reference对象。
-
Enqueued(濒死状态):每一个Reference对象内部会存在Reference-handler线程,不断轮询判断当前对象是否加入Pending队列,如果已经加入则将Reference对象从Pending队列出队,入队ReferenceQueue队列。并可以针对Reference子类实现做扩展(如Cleaner)
-
Inactive(凉凉状态):JVM会对ReferenceQueue队列中 reference引用对象做清理。此时reference引用对象变为Inactive
reference引用状态图
imagereference对象状态图
Reference对象生命周期.jpg-
Active(活跃状态):Reference对象被创建属性next==null
-
Pending(半死不活状态):Reference对象属性next != null,queue != ReferenceQueue.NULL && queue != ReferenceQueue.ENQUEUED,Pending!=null(第一个节点)
-
Enqueued(濒死状态):Reference对象属性next!=null,queue == ReferenceQueue.ENQUEUED
-
Inactive(凉凉状态):Reference对象属性next!=null,queue == ReferenceQueue.NULL;
5 ReferenceHandler线程
ReferenceHandler类是Reference类的一个静态内部类,继承自Thread,所以这条线程就叫它ReferenceHandler线程。
用来不断轮询判断当前对象是否加入Pending队列,如果已经加入则将Reference对象从Pending队列出队,入队ReferenceQueue队列。并可以针对Reference子类实现做扩展
初始化启动
Reference内部通过静态代码块初始化并启动ReferenceHandler线程
static {
/** 获取当前线程最高线程父类的分组 **/
ThreadGroup tg = Thread.currentThread().getThreadGroup();
for (ThreadGroup tgn = tg;
tgn != null;
tg = tgn, tgn = tg.getParent());
/** 实例ReferenceHandler并启动ReferenceHandler线程 **/
Thread handler = new 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);
}
});
}
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 {
/** 初始化InterruptedException **/
ensureClassInitialized(InterruptedException.class);
/** 初始化Cleaner **/
ensureClassInitialized(Cleaner.class);
}
ReferenceHandler(ThreadGroup g, String name) {
super(g, name);
}
/** run **/
public void run() {
/** 死循环调用 **/
while (true) {
tryHandlePending(true);
}
}
}
ReferenceHandler执行逻辑
判断是当前对象是否加入pending,且是队列首节点,如果是加入ReferenceQueue队列,同时如果Reference实现为Cleaner,调用clean方法做清理工作
static boolean tryHandlePending(boolean waitForNotify) {
Reference<Object> r;
Cleaner c;
try {
synchronized (lock) {
/** 判断是当前对象是否加入pending,且是队列首节点 **/
if (pending != null) {
/** 当前对象从pending队列出队 **/
r = pending;
pending = r.discovered;
r.discovered = null;
/** 判断当前对象是否是Cleaner **/
c = r instanceof Cleaner ? (Cleaner) r : null;
} else {
/** 等待 **/
if (waitForNotify) {
lock.wait();
}
// retry if waited
return waitForNotify;
}
}
} catch (OutOfMemoryError x) {
Thread.yield();
return true;
} catch (InterruptedException x) {
return true;
}
/** Cleaner清理工作 **/
if (c != null) {
c.clean();
return true;
}
/** 加入ReferenceQueue队列 **/
ReferenceQueue<? super Object> q = r.queue;
if (q != ReferenceQueue.NULL) q.enqueue(r);
return true;
}
6 其他方法
/**
* referent引用
*/
public T get() {
return this.referent;
}
/**
* 清理referent引用
*/
public void clear() {
this.referent = null;
}
/**
* referent引用对象是否为Enqueued状态,referent对象添加到ReferenceQueue队列
*/
public boolean isEnqueued() {
return (this.queue == ReferenceQueue.ENQUEUED);
}
/**
* 将referent对象添加到ReferenceQueue队列
*/
public boolean enqueue() {
return this.queue.enqueue(this);
}