APP下载
报价宝  ›  科技  › 

读懂Java的Reference和ReferenceQueue

报价宝 来源:baojiabao.com 发布时间:2019-09-13 18:50:00 11月16日更新
报价宝综合消息读懂Java的Reference和ReferenceQueue

原文:https://blog.csdn.net/gdutxiaoxu/article/details/80738581

从基础讲起

Reference

主要是负责内存的一个状态,当然它还和java虚拟机器,垃圾回收器打交道。Reference类首先把内存分为4种状态Active,Pending,Enqueued,Inactive。

ReferenceQueue

引用伫列,在检测到适当的可到达性更改后,垃圾回收器将已注册的引用物件新增到伫列中,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 就是采用这种原理来检测的。

Refrence

主要内存成员变数

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 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 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 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 pending = null;

RefrenceQueue

接下来,我们在来看一下 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 的源代码分析到此为止

Refrence 的子类

4种引用 我们都知道在Java中有4种引用,这四种引用从高到低分别为: StrongReference

这个引用在Java中没有相应的类与之对应,但是强引用比较普遍,例如:Object obj = new Object();这里的obj就是要给强引用,如果一个物件具有强引用,则垃圾回收器始终不会回收此物件。当内存不足时,JVM情愿丢掷OOM异常使程式异常终止也不会靠回收强引用的物件来解决内存不足的问题。 SoftReference

如果一个物件只有软引用,则在内存充足的情况下是不会回收此物件的,但是,在内部不足即将要丢掷OOM异常时就会回收此物件来解决内存不足的问题。

public class TestSoftReference {

private static ReferenceQueue rq = new ReferenceQueue();

public static void main(String[] args){

Object obj = new Object();

SoftReference sf = new SoftReference(obj,rq);

System.out.println(sf.get()!=null);

System.gc();

obj = null;

System.out.println(sf.get()!=null);

}

}

执行结果均为:true。

这也就说明了当内存充足的时候一个物件只有软引用也不会被JVM回收。 WeakReference

WeakReference 基本与SoftReference 类似,只是回收的策略不同。

只要 GC 发现一个物件只有弱引用,则就会回收此弱引用物件。但是由于GC所在的执行绪优先级比较低,不会立即发现所有弱引用物件并进行回收。只要GC对它所管辖的内存区域进行扫描时发现了弱引用物件就进行回收。

看一个例子:

public class TestWeakReference {

private static ReferenceQueue rq = new ReferenceQueue();

public static void main(String[] args) {

Object obj = new Object();

WeakReference 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给回收了。 PhantomReference

PhantomReference,即虚引用,虚引用并不会影响物件的生命周期。虚引用的作用为:跟踪垃圾回收器收集物件这一活动的情况。

当GC一旦发现了虚引用物件,则会将PhantomReference物件插入ReferenceQueue伫列,而此时PhantomReference物件并没有被垃圾回收器回收,而是要等到ReferenceQueue被你真正的处理后才会被回收。

注意:PhantomReference必须要和ReferenceQueue联合使用,SoftReference和WeakReference可以选择和ReferenceQueue联合使用也可以不选择,这使他们的区别之一。

接下来看一个虚引用的例子。

public class TestPhantomReference {

private static ReferenceQueue rq = new ReferenceQueue();

public static void main(String[] args){

Object obj = new Object();

PhantomReference pr = new PhantomReference(obj, rq);

System.out.println(pr.get());

obj = null;

System.gc();

System.out.println(pr.get());

Reference r = (Reference)rq.poll();

if(r!=null){

System.out.println("回收");

}

}

}

执行结果:null null 回收

根据上面的例子有两点需要说明:

总结

Refrence 和引用伫列 ReferenceQueue 联合使用时,如果 Refrence持有的物件被垃圾回收,Java 虚拟机器就会把这个弱引用加入到与之关联的引用伫列中。

文章标签: 报价宝 降噪耳机价格 耳机价格 红米手机价格 华为手机价格 小米手机价格 电视机价格 笔记本电脑价格 笔记本价格 汽车价格 手机价格 笔记本电脑价格 小米手机价格 红米手机价格 降噪耳机价格