版权声明:本文为博主原创文章,未经博主允许不得转载。 https://cloud.tencent.com/developer/article/1340318
java 源码系列 - 带你读懂 Reference 和 ReferenceQueue
https://cloud.tencent.com/developer/article/1340318
一步步拆解 LeakCanary
https://cloud.tencent.com/developer/article/1340319
主要是负责内存的一个状态,当然它还和java虚拟机,垃圾回收器打交道。Reference类首先把内存分为4种状态Active,Pending,Enqueued,Inactive。
引用队列,在检测到适当的可到达性更改后,垃圾回收器将已注册的引用对象添加到队列中,ReferenceQueue实现了入队(enqueue)和出队(poll),还有remove操作,内部元素head就是泛型的Reference。
当我们想检测一个对象是否被回收了,那么我们就可以采用 Reference + ReferenceQueue,大概需要几个步骤:
创建一个引用队列
ReferenceQueue queue = new ReferenceQueue();
// 创建弱引用,此时状态为Active,并且Reference.pending为空,当前Reference.queue = 上面创建的queue,并且next=null
WeakReference reference = new WeakReference(new Object(), queue);
System.out.println(reference);
// 当GC执行后,由于是虚引用,所以回收该object对象,并且置于pending上,此时reference的状态为PENDING
System.gc();
/* ReferenceHandler从pending中取下该元素,并且将该元素放入到queue中,此时Reference状态为ENQUEUED,Reference.queue = ReferenceENQUEUED */
/* 当从queue里面取出该元素,则变为INACTIVE,Reference.queue = Reference.NULL */
Reference reference1 = queue.remove();
System.out.println(reference1);
那这个可以用来干什么了?
可以用来检测内存泄露, github 上面 的 leekCanary 就是采用这种原理来检测的。
private T referent;
volatile ReferenceQueue<? super T> queue;
/* When active: NULL
* pending: this
* Enqueued: next reference in queue (or this if last)
* Inactive: this
*/
@SuppressWarnings("rawtypes")
Reference next;
transient private Reference<T> discovered; /* used by VM */
/* 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;
接下来,我们来看一下 Refrence 的静态代码块
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);
}
});
}
我们,当 Refrence 类被加载的时候,会执行静态代码块。在静态代码块里面,会启动 ReferenceHandler 线程,并设置线程的级别为最大级别, Thread.MAX_PRIORITY。
接下来我们来看一下 ReferenceHandler 这个类,可以看到 run 方法里面是一个死循环,我们主要关注 tryHandlePending 方法就 Ok 了
private static class ReferenceHandler extends Thread {
----- // 核心代码如下
public void run() {
while (true) {
tryHandlePending(true);
}
}
}
static boolean tryHandlePending(boolean waitForNotify) {
Reference<Object> r;
Cleaner c;
try {
synchronized (lock) {
// 检查 pending 是否为 null,不为 null,制定 pending enqueue
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 { // 为 null。等待
// 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;
}
在 tryHandlePending 方法里面,检查 pending 是否为 null,如果pending不为 null,则将 pending 进行 enqueue,否则线程进入 wait 状态。
问题来了,我们从 Reference 源码中发现没有给 discovered和 pending 赋值的地方,那 pending和 discovered 到底是谁给他们赋值的。
我们回头再来看一下注释:简单来说,垃圾回收器会把 References 添加进入,Reference-handler thread 会移除它,即 discovered和 pending 是由垃圾回收器进行赋值的。
/* 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;
接下来,我们在来看一下 RefrenceQueue 的 enqueue 方法
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;
// queue 为 null 或者 queue 已经被回收了,直接返回
if ((queue == NULL) || (queue == ENQUEUED)) {
return false;
}
assert queue == this;
// 将 refrence 的状态置为 Enqueued,表示已经被回收
r.queue = ENQUEUED;
// 接着,将 refrence 插入到链表
// 判断当前链表是否为 null,不为 null,将 r.next 指向 head,为 null,head 直接指向 r
r.next = (head == null) ? r : head;
// head 指针指向 r
head = r;
queueLength++;
if (r instanceof FinalReference) {
sun.misc.VM.addFinalRefCount(1);
}
lock.notifyAll();
return true;
}
}
Refrence 和 RefrenceQueue 的源码分析到此为止
4种引用
我们都知道在Java中有4种引用,这四种引用从高到低分别为:
1) StrongReference
这个引用在Java中没有相应的类与之对应,但是强引用比较普遍,例如:Object obj = new Object();这里的obj就是要给强引用,如果一个对象具有强引用,则垃圾回收器始终不会回收此对象。当内存不足时,JVM情愿抛出OOM异常使程序异常终止也不会靠回收强引用的对象来解决内存不足的问题。
2) SoftReference
如果一个对象只有软引用,则在内存充足的情况下是不会回收此对象的,但是,在内部不足即将要抛出OOM异常时就会回收此对象来解决内存不足的问题。
public class TestSoftReference {
private static ReferenceQueue<Object> rq = new ReferenceQueue<Object>();
public static void main(String[] args){
Object obj = new Object();
SoftReference<Object> sf = new SoftReference(obj,rq);
System.out.println(sf.get()!=null);
System.gc();
obj = null;
System.out.println(sf.get()!=null);
}
}
运行结果均为:true。
这也就说明了当内存充足的时候一个对象只有软引用也不会被JVM回收。
3) WeakReference
WeakReference 基本与SoftReference 类似,只是回收的策略不同。
只要 GC 发现一个对象只有弱引用,则就会回收此弱引用对象。但是由于GC所在的线程优先级比较低,不会立即发现所有弱引用对象并进行回收。只要GC对它所管辖的内存区域进行扫描时发现了弱引用对象就进行回收。
看一个例子:
public class TestWeakReference {
private static ReferenceQueue<Object> rq = new ReferenceQueue<Object>();
public static void main(String[] args) {
Object obj = new Object();
WeakReference<Object> wr = new WeakReference(obj,rq);
System.out.println(wr.get()!=null);
obj = null;
System.gc();
System.out.println(wr.get()!=null);//false,这是因为WeakReference被回收
}
}
运行结果为: true 、false
在指向 obj = null 语句之前,Object对象有两条引用路径,其中一条为obj强引用类型,另一条为wr弱引用类型。此时无论如何也不会进行垃圾回收。当执行了obj = null.Object 对象就只具有弱引用,并且我们进行了显示的垃圾回收。因此此具有弱引用的对象就被GC给回收了。
4) PhantomReference
PhantomReference,即虚引用,虚引用并不会影响对象的生命周期。虚引用的作用为:跟踪垃圾回收器收集对象这一活动的情况。
当GC一旦发现了虚引用对象,则会将PhantomReference对象插入ReferenceQueue队列,而此时PhantomReference对象并没有被垃圾回收器回收,而是要等到ReferenceQueue被你真正的处理后才会被回收。
注意:PhantomReference必须要和ReferenceQueue联合使用,SoftReference和WeakReference可以选择和ReferenceQueue联合使用也可以不选择,这使他们的区别之一。
接下来看一个虚引用的例子。
public class TestPhantomReference {
private static ReferenceQueue<Object> rq = new ReferenceQueue<Object>();
public static void main(String[] args){
Object obj = new Object();
PhantomReference<Object> pr = new PhantomReference<Object>(obj, rq);
System.out.println(pr.get());
obj = null;
System.gc();
System.out.println(pr.get());
Reference<Object> r = (Reference<Object>)rq.poll();
if(r!=null){
System.out.println("回收");
}
}
}
运行结果:null null 回收
根据上面的例子有两点需要说明:
Refrence 和引用队列 ReferenceQueue 联合使用时,如果 Refrence持有的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。