引用队列(ReferenceQueue)浅析

什么是引用队列

引用队列,注册的引用型对象(SoftReference、WeakReference、PhantomReference)在垃圾回收器检测到所引用的对象可达性发生改变时,会将这个引用型的对象添加到引用队列中。

可达性改变是什么意思

我们都知道:垃圾回收器会清理掉被弱引用引用着的对象,或者说是具有弱可达性的对象来作为垃圾回收的过程中的一部分。该过程可以这样描述:
如果垃圾回收器发现一个对象具有弱可达性,那么它会
1. 将弱引用(WeakReference)对象的实际引用字段设置为null,因此可以使它不再引用着堆中的对象。
2. 被弱引用引用着的堆中的对象会被声明为是可终结的(finalizable)。
3. 弱引用对象被添加到它的引用队列中。然后在堆中的那个对象会调用finalize()方法,它的内存会被释放掉。

那么引用队列究竟是什么

当创建一个非强引用的引用对象时,可以传一个引用队列对象给Reference构造函数。引用队列是GC通知程序某个对象不可达的信号,装载这个不可达对象引用的容器。
示例代码如下:

public static void main(String[] args) throws InterruptedException {
        SavePoint savePoint = new SavePoint("Random"); // 创建一个强引用
        ReferenceQueue<SavePoint> savepointQ = new ReferenceQueue<SavePoint>();//引用队列
        WeakReference<SavePoint> savePointWRefernce = new WeakReference<SavePoint>(savePoint, savepointQ);
        System.out.println("SavePoint 被作为一个弱引用来创建" + savePointWRefernce);
        Runtime.getRuntime().gc();
        System.out.println("在引用队列中存在引用型对象吗 ? " + (savepointQ.poll() != null));
        savePoint = null; // 唯一的强引用被删除掉,在堆中的对象现在只具有弱可达性
        System.out.println("现在调用GC");
        Runtime.getRuntime().gc(); // 对象会在这里被回收掉,finalize方法会被调用
        System.out.println("在引用队列中有任何弱引用吗? " + (savepointQ.remove() != null));
        System.out.println("弱引用型对象还引用着堆中的对象吗?" + (savePointWRefernce.get() != null));
        System.out.println("弱引用型对象被添加到引用队列中了吗?" + (savePointWRefernce.isEnqueued()));
    }

代码说明如下:
创建一个强引用对象,并将它添加到弱引用中。这个在内存中的对象现在同时被一个强引用和一个弱引用引用着,因此它依然具有强可达性。
首次调用GC并不会清除掉savepoint对象,因为它仍然具有强可达性。因此referenceQ的poll方法会返回null。(poll方法是非阻塞的方法,它会立即返回)
savePoint引用型变量被置为null。现在堆中的对象只被弱引用型变量引用着,因此它现在具有弱可达性。
再次调用GC那么就会定位到这个对象上,然后执行它的finalize方法,将它标记为要被释放掉。然后将弱引用型的对象添加进引用队列中。
调用引用队列的remove方法会返回这个对象,并且这个remove方法是一个阻塞方法,它会一直等到在队列中有可用的引用型对象。(poll方法不会像回收过程那样发生在分离的线程中)
运行结果如下:

SavePoint 被作为一个弱引用来创建java.lang.ref.WeakReference@2a139a55
在引用队列中存在引用型对象吗 ? false
现在调用GC
在引用队列中有任何弱引用吗? true
弱引用型对象还引用着堆中的对象吗?false
弱引用型对象被添加到引用队列中了吗?false

一旦某个对象被释放掉,它的引用型对象会被添加到引用队列,因此引用队列就像是java程序中的回调函数。这样就能告诉程序员:一个特定的对象已经从它的引用中被释放掉了,我们的代码不能再次引用到这个对象了。
那么我们能够再次使用用引用队列中的对象吗
我们知道,finalize方法可以被用来重新依附到对象上,使他们具有强可达性。例如将它们添加到静态列表中。那么这个对象在本次循环中不会再被回收掉。让我们尝试着回收这个对象:
实例代码如下:

public static void main(String[] args) throws InterruptedException {
        SavePoint savePoint = new SavePoint("Random"); // 创建一个强引用
        ReferenceQueue<SavePoint> savepointQ = new ReferenceQueue<SavePoint>();// 引用队列
        WeakReference<SavePoint> savePointWRefernce = new WeakReference<SavePoint>(savePoint, savepointQ);
        System.out.println("在队列中有任何弱引用吗? " + (savepointQ.poll() != null));
        savePoint = null;
        System.out.println("现在调用GC...");
        Runtime.getRuntime().gc(); // 对象在这里会被清除掉 - finalize方法会被调用
        Reference<? extends SavePoint> reCreatedSavePoint = savepointQ.remove();
        System.out.println("在队列中存在任何弱引用吗 ? " + (reCreatedSavePoint != null));
        System.out.println("这个引用和原先的弱引是同一个对象吗 ? " + (reCreatedSavePoint == savePointWRefernce));
        System.out.println("堆中对象是: " + reCreatedSavePoint.get());
    }

上述代码尝试着从引用队列中返回一个已经释放了的对象。
输出结果如下:

在队列中有任何弱引用吗? false
现在调用GC...
在队列中存在任何弱引用吗 ? true
这个引用和原先的弱引是同一个对象吗 ? true
堆中对象是: null

正如上面输出结果所看到的那样,引用队列实际上只是持有着已经不再引用堆中的要被清除的对象的引用型对象。因此这个弱引用和内存中的对象没有任何关联。调用get会返回null。并不像finalize方法那样还可以使对象活下去,因此使用引用队列不可能再次获得被释放的对象。引用队列只是为了被GC回收的对象的引用型对象,而使用它并不能使对象再次存活下去,其用处只是为了提醒程序员非强引用型变量所引用的对象已经具有不可达性了,再也不能获得堆中的那个对象了。

  • 15
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值