定义

ReferenceQueue是引用队列,用于存放待回收的引用对象。

说明

对于软引用、弱引用和虚引用,如果我们希望当一个对象被垃圾回收器回收时能得到通知,进行额外的处理,这时候就需要使用到引用队列了。

在一个对象被垃圾回收器扫描到将要进行回收时,其相应的引用包装类,即reference对象会被放入其注册的引用队列queue中。可以从queue中获取到相应的对象信息,同时进行额外的处理。比如反向操作,数据清理,资源释放等。

使用例子

  1. public class ReferenceQueueTest {
  2. private static ReferenceQueue<byte[]> rq = new ReferenceQueue<>();
  3. private static int _1M = 1024 * 1024;
  4. public static void main(String[] args) {
  5. Object value = new Object();
  6. Map<WeakReference<byte[]>, Object> map = new HashMap<>();
  7. Thread thread = new Thread(ReferenceQueueTest::run);
  8. thread.setDaemon(true);
  9. thread.start();
  10. for(int i = 0;i < 100;i++) {
  11. byte[] bytes = new byte[_1M];
  12. WeakReference<byte[]> weakReference = new WeakReference<>(bytes, rq);
  13. map.put(weakReference, value);
  14. }
  15. System.out.println("map.size->" + map.size());
  16. int aliveNum = 0;
  17. for (Map.Entry<WeakReference<byte[]>, Object> entry : map.entrySet()){
  18. if (entry != null){
  19. if (entry.getKey().get() != null){
  20. aliveNum++;
  21. }
  22. }
  23. }
  24. System.out.println("100个对象中存活的对象数量:" + aliveNum);
  25. }
  26. private static void run() {
  27. try {
  28. int n = 0;
  29. WeakReference k;
  30. while ((k = (WeakReference) rq.remove()) != null) {
  31. System.out.println((++n) + "回收了:" + k);
  32. }
  33. } catch (InterruptedException e) {
  34. e.printStackTrace();
  35. }
  36. }
  37. }

这里有一个小栗子,main方法中,创建了一条线程,使用死循环来从引用队列中获取元素,监控对象被回收的状态。然后循环往map中添加了100个映射关系,以下是运行结果:

  1. ...前面省略了大量相似输出
  2. 85回收了:java.lang.ref.WeakReference@7106e68e
  3. 86回收了:java.lang.ref.WeakReference@1f17ae12
  4. 87回收了:java.lang.ref.WeakReference@c4437c4
  5. map.size->100
  6. 100个对象中存活的对象数量:12

通过配合使用ReferenceQueue,可以较好的监控对象的生存状态。

成员变量

ReferenceQueue中内部成员变量也很少,主要有这么几个:

  1. static ReferenceQueue<Object> NULL = new Null<>();
  2. static ReferenceQueue<Object> ENQUEUED = new Null<>();

有两个用来做为特殊标记的静态成员变量,一个是NULL,一个是ENQUEUE,上一篇中说的ReferenceQueue.NULL和ReferenceQueue.ENQUEUED就是这两个家伙。

来看看Null长什么样:

  1. private static class Null<S> extends ReferenceQueue<S> {
  2. boolean enqueue(Reference<? extends S> r) {
  3. return false;
  4. }
  5. }

只是简单继承了ReferenceQueue的一个类,emmm,为什么不直接new一个ReferenceQueue呢?这里自然是有它的道理的,如果直接使用ReferenceQueue,就会导致有可能误操作这个NULL和ENQUEUED变量,因为ReferenceQueue中enqueue方法是需要使用lock对象锁的,这里覆盖了这个方法并直接返回false,这样就避免了乱用的可能性,也避免了不必要的资源浪费。

  1. static private class Lock { };
  2. private Lock lock = new Lock();

跟Reference一样,有一个lock对象用来做同步对象。

  1. private volatile Reference<? extends T> head = null;

head用来保存队列的头结点,因为Reference是一个单链表结构,所以只需要保存头结点即可。

  1. private long queueLength = 0;

queueLength用来保存队列长度,在添加元素的时候+1,移除元素的时候-1,因为在添加和移除操作的时候都会使用synchronized进行同步,所以不用担心多线程修改会不会出错的问题。

内部方法

  1. // 这个方法仅会被Reference类调用
  2. boolean enqueue(Reference<? extends T> r) {
  3. synchronized (lock) {
  4. // 检测从获取这个锁之后,该Reference没有入队,并且没有被移除
  5. ReferenceQueue<?> queue = r.queue;
  6. if ((queue == NULL) || (queue == ENQUEUED)) {
  7. return false;
  8. }
  9. assert queue == this;
  10. // 将reference的queue标记为ENQUEUED
  11. r.queue = ENQUEUED;
  12. // 将r设置为链表的头结点
  13. r.next = (head == null) ? r : head;
  14. head = r;
  15. queueLength++;
  16. // 如果r的FinalReference类型,则将FinalRef+1
  17. if (r instanceof FinalReference) {
  18. sun.misc.VM.addFinalRefCount(1);
  19. }
  20. lock.notifyAll();
  21. return true;
  22. }
  23. }

这里是入队的方法,使用了lock对象锁进行同步,将传入的r添加到队列中,并重置头结点为传入的节点。

  1. public Reference<? extends T> poll() {
  2. if (head == null)
  3. return null;
  4. synchronized (lock) {
  5. return reallyPoll();
  6. }
  7. }
  8. private Reference<? extends T> reallyPoll() {
  9. Reference<? extends T> r = head;
  10. if (r != null) {
  11. head = (r.next == r) ?
  12. null : r.next;
  13. r.queue = NULL;
  14. r.next = r;
  15. queueLength--;
  16. if (r instanceof FinalReference) {
  17. sun.misc.VM.addFinalRefCount(-1);
  18. }
  19. return r;
  20. }
  21. return null;
  22. }

poll方法将头结点弹出。嗯,没错,弹出的是头结点而不是尾节点,名义上,它叫ReferenceQueue,实际上是一个ReferenceStack(滑稽)。惊不惊喜,意不意外。

  1. /**
  2. * 移除并返回队列首节点,此方法将阻塞到获取到一个Reference对象或者超时才会返回
  3. * timeout时间的单位是毫秒
  4. */
  5. public Reference<? extends T> remove(long timeout)
  6. throws IllegalArgumentException, InterruptedException{
  7. if (timeout < 0) {
  8. throw new IllegalArgumentException("Negative timeout value");
  9. }
  10. synchronized (lock) {
  11. Reference<? extends T> r = reallyPoll();
  12. if (r != null) return r;
  13. long start = (timeout == 0) ? 0 : System.nanoTime();
  14. // 死循环,直到取到数据或者超时
  15. for (;;) {
  16. lock.wait(timeout);
  17. r = reallyPoll();
  18. if (r != null) return r;
  19. if (timeout != 0) {
  20. // System.nanoTime方法返回的是纳秒,1毫秒=1纳秒*1000*1000
  21. long end = System.nanoTime();
  22. timeout -= (end - start) / 1000_000;
  23. if (timeout <= 0) return null;
  24. start = end;
  25. }
  26. }
  27. }
  28. }
  29. /**
  30. * 移除并返回队列首节点,此方法将阻塞到获取到一个Reference对象才会返回
  31. */
  32. public Reference<? extends T> remove() throws InterruptedException {
  33. return remove(0);
  34. }

这里两个方法都是从队列中移除首节点,与poll不同的是,它会阻塞到超时或者取到一个Reference对象才会返回。

聪明的你可能会想到,调用remove方法的时候,如果队列为空,则会一直阻塞,也会一直占用lock对象锁,这个时候,有引用需要入队的话,不就进不来了吗?

嗯,讲道理确实是这样的,但是注意注释,enqueue只是给Reference调用的,在Reference的public方法enqueue中可以将该引用直接入队,但是虚拟机作为程序的管理者可不吃这套,而是通过其它方式将Reference对象塞进去的,所以才会出现之前的栗子中,死循环调用remove方法,并不会阻塞引用进入队列中的情况。

应用场景

ReferenceQueue一般用来与SoftReference、WeakReference或者PhantomReference配合使用,将需要关注的引用对象注册到引用队列后,便可以通过监控该队列来判断关注的对象是否被回收,从而执行相应的方法。

主要使用场景:

1、使用引用队列进行数据监控,类似前面栗子的用法。

2、队列监控的反向操作

反向操作,即意味着一个数据变化了,可以通过Reference对象反向拿到相关的数据,从而进行后续的处理。下面有个小栗子:

  1. public class TestB {
  2. private static ReferenceQueue<byte[]> referenceQueue = new ReferenceQueue<>();
  3. private static int _1M = 1024 * 1024;
  4. public static void main(String[] args) throws InterruptedException {
  5. final Map<Object, MyWeakReference> hashMap = new HashMap<>();
  6. Thread thread = new Thread(() -> {
  7. try {
  8. int n = 0;
  9. MyWeakReference k;
  10. while(null != (k = (MyWeakReference) referenceQueue.remove())) {
  11. System.out.println((++n) + "回收了:" + k);
  12. //反向获取,移除对应的entry
  13. hashMap.remove(k.key);
  14. //额外对key对象作其它处理,比如关闭流,通知操作等
  15. }
  16. } catch(InterruptedException e) {
  17. e.printStackTrace();
  18. }
  19. });
  20. thread.setDaemon(true);
  21. thread.start();
  22. for(int i = 0;i < 10000;i++) {
  23. byte[] bytesKey = new byte[_1M];
  24. byte[] bytesValue = new byte[_1M];
  25. hashMap.put(bytesKey, new MyWeakReference(bytesKey, bytesValue, referenceQueue));
  26. }
  27. }
  28. static class MyWeakReference extends WeakReference<byte[]> {
  29. private Object key;
  30. MyWeakReference(Object key, byte[] referent, ReferenceQueue<? super byte[]> q) {
  31. super(referent, q);
  32. this.key = key;
  33. }
  34. }
  35. }

这里通过referenceQueue监控到有引用被回收后,通过map反向获取到对应的value,然后进行资源释放等。

小结

  • ReferenceQueue是用来保存需要关注的Reference队列
  • ReferenceQueue内部实现实际上是一个栈
  • ReferenceQueue可以用来进行数据监控,资源释放等

你不可不知的Java引用类型之——ReferenceQueue源码详解的更多相关文章

  1. 你不可不知的Java引用类型之——SoftReference源码详解

    定义 SoftReference是软引用,其引用的对象在内存不足的时候会被回收.只有软引用指向的对象称为软可达(softly-reachable)对象. 说明 垃圾回收器会在内存不足,经过一次垃圾回收 ...

  2. 你不可不知的Java引用类型之——PhantomReference源码详解

    定义 PhantomReference是虚引用,该引用不会影响不会影响对象的生命周期,也无法从虚引用中获取对象实例. 说明 源码介绍部分其实也没多大内容,主要内容都在前面介绍中说完了.PhantomR ...

  3. 你不可不知的Java引用类型之——WeakReference源码详解

    定义 WeakReference是弱引用,该引用不会影响垃圾回收器对对象的回收,不会影响对象的生命周期. 说明 当虚拟机在某个时间点决定要回收一个弱可达(weakly-reachable)对象时,会自 ...

  4. 数据结构与算法系列2 线性表 链表的分类+使用java实现链表+链表源码详解

    数据结构与算法系列2.2 线性表 什么是链表? 链表是一种物理存储单元上非连续,非顺序的存储结构,数据元素的逻辑顺序是通过链表的链接次序实现的一系列节点组成,节点可以在运行时动态生成,每个节点包括两个 ...

  5. 你不可不知的Java引用类型之——Reference源码解析

    定义 Reference是所有引用类型的父类,定义了引用的公共行为和操作. reference指代引用对象本身,referent指代reference引用的对象,下文介绍会以reference,ref ...

  6. 数据结构与算法系列2 线性表 使用java实现动态数组+ArrayList源码详解

    数据结构与算法系列2 线性表 使用java实现动态数组+ArrayList源码详解 对数组有不了解的可以先看看我的另一篇文章,那篇文章对数组有很多详细的解析,而本篇文章则着重讲动态数组,另一篇文章链接 ...

  7. Java集合【6.1】-- Collection接口源码详解

    目录 一.Collection接口简介 二.Collection源码分析 三.Collection的子类以及子类的实现 3.1 List extend Collection 3.2 Set exten ...

  8. Java多线程学习之线程池源码详解

    0.使用线程池的必要性 在生产环境中,如果为每个任务分配一个线程,会造成许多问题: 线程生命周期的开销非常高.线程的创建和销毁都要付出代价.比如,线程的创建需要时间,延迟处理请求.如果请求的到达率非常 ...

  9. Java集合——TreeMap源码详解

    )TreeMap 是一个有序的key-value集合,它是通过红黑树实现的.因为红黑树是平衡的二叉搜索树,所以其put(包含update操作).get.remove的时间复杂度都为log(n). (2 ...

随机推荐

  1. homebrew 更改镜像,进行成功安装

    在mac系统中,使用homebrew可以很方便的管理包.按照官网的说明执行以下命令时总是报错: /usr/bin/ruby -e "$(curl -fsSL https://raw.gith ...

  2. 使用 DryIoc 替换 Abp 的 DI 框架

    一.背景 你说我 Castle Windsor 库用得好好的,为啥要大费周章的替换成 DryIoc 库呢?那就是性能,DryIoc 是一款优秀而且轻量级的 DI 框架,整个项目代码就两个文件,加起来代 ...

  3. JVM读书笔记之内存管理

    对于从事C.C++程序开发人员来说,在内存管理领域,他们既是拥有最高权力的“皇帝”又是从事最基础工作的“劳动人民”--既拥有每一个对象的“所有权”,又负责每一个对象生命开始到终结的维护责任. 对于Ja ...

  4. .NET西安社区 [拥抱开源,又见 .NET] 活动简报

    拥抱开源, 又见 .NET」 随着 .NET Core的发布和开源,.NET又重新回到了人们的视野.除了开源.跨平台.高性能以及优秀的语言特性,越来越多的第三方开源库也出现在了Github上——包括M ...

  5. java web路径和spring读取配置文件

    此篇博客缘起:部署java web系统到阿里云服务器(ubuntu14.04)的时候,有以下两个问题 找不到自定义的property配置文件 上传图片的时候找不到路径 开发的时候是在windows上的 ...

  6. Linux官方源、镜像源汇总

    本文收录在日常运维杂烩系列 一.站点版 1.企业站 搜狐:http://mirrors.sohu.com/ 网易:http://mirrors.163.com/ 阿里云:http://mirrors. ...

  7. 浅谈缓存技术在ASP.NET中的运用

    本篇文章虽不谈架构,但是Cache又是架构中不可或缺的部分,因此,在讲解Cache的同时,将会提及到部分架构知识,关于架构部分,读者可以不用理解,或者直接跳过, 你只需关心Cache即可,具体的架构, ...

  8. python重试库retryiny源码剖析

    上篇博文介绍了常见需要进行请求重试的场景,本篇博文试着剖析有名的python第三方库retrying源码. 在剖析其源码之前,有必要讲一下retrying的用法,方便理解. 安装: pip insta ...

  9. Python多进程操作同一个文件,文件锁问题

    最近工作当中做了一个项目,这个项目主要是操作文件的. 在操作耗时操作的时候,我们一般采用多线程或者多进程.在开发中,如果多个线程需要对文件进行读写操作,就需要用到线程锁或者是文件锁. 使用fcntl ...

  10. 序言:我为什么学Perl

    曾经,我熟练操作grep.awk.sed,甚至自认对sed尚算精通,我一度爱上了写脚本.但是随着写脚本的次数多了,需求复杂了,我深深的感受到shell的无奈. 例如,我多次遇到过类似下面这种恶心的需求 ...