背景
在java中引用分为四种,强引用,软引用,弱引用,虚引用。强引用是平时用到最多的,最常见的就是我们使用new去创建的对象然后赋值给到的引用就是强引用,例如Object obj = new Object(), obj就是一个强引用。对于SoftReference,WeakReference,PhantomReference 这三种类型的引用类型他们都继承自Reference这个抽象类。
引用方式
ReferenceQueue referenceQueue = new ReferenceQueue();
Object obj = new Object();
SoftReference sr = new SoftReference(obj,referenceQueue);
WeakReference wr = new WeakReference(obj,referenceQueue);
PhantomReference pr = new PhantomReference(obj,referenceQueue)
从上面的代码我们可以看出,这三种引用提供了基本相同的使用方式,当然,对于SoftReference,WeakReference来说ReferenceQueue在初始化引用的时候不是必须的,因为如果你不提供的话,SoftReference,WeakReference的父类Reference会提供默认的实现:
image.png
image.png
作用
这三种引用类型引用的对象如果处于gc root链不可达,那么这些对象就有可能被随时被回收掉。对于SoftReference引用的对象来说,如果gc root链不可达,那么垃圾收集器会在内存不足的时候把它引用的对象回收掉。对于WeakReference和PhantomReference来说他们引用的对象,如果gc root链不可达那么在gc的时候会被立即回收。SoftReference和WeakReference引用的对象可以通过引用的get方法获取到相应的对象,但是对于PhantomReference来说没法通过这类引用去获取被引用的对象。当这三类引用引用的对象被回收的时候,那么这三类引用对象自身会被放在ReferenceQueue中,然后我们可以从ReferenceQueue获取相应的引用对象信息,基于这写信息我们可以做一些自己的逻辑。
回收过程
-
Reference
这个类是一个链表节点类型的类
image.png -
ReferenceHandler
referenceHandler是Reference的内部类继承Thread,
这个类是在Reference的静态代码块中被启动的
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();
// provide access in SharedSecrets
SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
@Override
public boolean tryHandlePendingReference() {
return tryHandlePending(false);
}
});
}
它的run方法如下
public void run() {
while (true) {
tryHandlePending(true);
}
}
tryHandlePending发生了什么呢
static boolean tryHandlePending(boolean waitForNotify) {
Reference<Object> r;
Cleaner c;
try {
synchronized (lock) {
if (pending != null) {
r = pending;
// 'instanceof' might throw OutOfMemoryError sometimes
// so do this before un-linking 'r' from the 'pending' chain...
c = r instanceof Cleaner ? (Cleaner) r : null;
// unlink 'r' from 'pending' chain
pending = r.discovered;
r.discovered = null;
} else {
// The waiting on the lock may cause an OutOfMemoryError
// because it may try to allocate exception objects.
if (waitForNotify) {
lock.wait();
}
// retry if waited
return waitForNotify;
}
}
} catch (OutOfMemoryError x) {
// Give other threads CPU time so they hopefully drop some live references
// and GC reclaims some space.
// Also prevent CPU intensive spinning in case 'r instanceof Cleaner' above
// persistently throws OOME for some time...
Thread.yield();
// retry
return true;
} catch (InterruptedException x) {
// retry
return true;
}
// Fast path for cleaners
if (c != null) {
c.clean();
return true;
}
ReferenceQueue<? super Object> q = r.queue;
if (q != ReferenceQueue.NULL) q.enqueue(r);
return true;
}
这个是一个核心方法,如果明白这里面发生了什么,那么就能搞明白这三类引用类型引用的对象被回收后发生了什么。
我们看到同步代码块中上来就是pending是否为空的判断,如果为空那么这个线程就去wait了,我们看下pending在Reference的定义
private static Reference<Object> pending = null;
可以看出初始化是null的,那么什么时候这个pending会被谁在什么时候赋值。
pending会被JVM直接赋值:当着三类引用类型引用的对象被回收后那么相应的引用会被加入到pending中,pending每次只会被赋值一个引用对象,那么剩下别的引用对象会被加入到pending的discover中形成一个链表。我们可以看到在tryHandlePending每次都是处理链表上的一个reference
image.png
那么对每一个reference是如何处理的呢,对于reference如果是一个Cleaner那么会执行它的clean方法
image.png
Cleaner是PhantomReference的子类,在DirectByteBuffer中就用到了它来做堆外内存的回收,在这里就不详细介绍了,如果不是Cleaner那么就是把这个reference加入到ReferenceQueue中
image.png
然后我们就可以从ReferenceQueue中获取相应的reference了
到此已经全部讲完了整个过程下面是我的实验代码
实验
Object obj = new Object();
ReferenceQueue referenceQueue = new ReferenceQueue();
WeakReference weakReference = new WeakReference(obj,referenceQueue);
WeakReference weakReference2 = new WeakReference(obj,referenceQueue);
WeakReference weakReference3 = new WeakReference(obj,referenceQueue);
System.out.println(weakReference.get());
System.out.println(referenceQueue.poll());
obj = null ;
System.gc();
System.out.println("before isEnqueue----"+weakReference.isEnqueued());
Thread.sleep(1000);
System.out.println("after isEnqueue----"+weakReference.isEnqueued());
Reference t = referenceQueue.poll() ;
System.out.println(t);
System.out.println(weakReference.get());
代码比较简单,使用WeakReference做示例,new一个对象然后使用强引用obj引用它,接下来建立三个弱引用引用这个对象,这个时候我们通过weakReference.get()是可以获得这个对象的,同时通过referenceQueue.poll()是获取不到任何数据的,这个时候我们把强引用obj = null,然后通过System.gc()触发full gc,这个时候理论上新建的new Object()对象会被回收掉,然后在referenceQueue会得到执行这个对象的三个弱引用,但是因为ReferenceHandler是一个后台线程在运行,所以我们在before isEnqueue打印出的是false(reference.isEnqueue可以判断这个reference是不是被加入了ReferenceQueue中),所以我们sleep 1s等一下ReferenceQueue线程去执行,在after isEnqueue 打印出的就是true,然后我们可以通过referenceQueue.poll()获得一个weakReference,如果你poll三次那么会得到上面定义的三个weakReference。
Tips:如果想debug tryHandlePending方法看到上面我们定义的weakReference是如何加入referenceQueue的,那么最好是把debug设置condition
image.png
上图是我们在idea 中设置的condition(pending instanceof WeakReference && !(pending instanceof WeakHashMap.Entry))为什么这么设置呢,因为jvm内部也有很多这种类型的引用会被回收,如果不设置就需要执行这个方法很多遍才能看到自己定义的reference
网友评论