话说ReferenceQueue
也是几年前写的,在内部邮件列表里发过,在这里保存一下。
看到了这篇帖子: 《WeakHashMap的神话》http://www.javaeye.com/topic/587995
因为Javaeye回帖还要先做个论坛小测验,所以懒得在上面回复了,在这里说下。
以前设计缓存时也曾过用WeakHashMap来实现,对Java的Reference
稍做过一些了解,其实这个问题,归根到底,是个Java
的问题,由垃圾回收器与
GCReferenceQueue
的交互方式决定的。WeakHashMap
的实现也是通过ReferenceQueue
这个“监听器”来优雅的实现自动删除那些引用不可达的key的。
先看看ReferenceQueue
在Java中的描述:
Reference queues, to which registered reference objects are appended by the garbage collector after the appropriate reachability changes are detected.
中文JavaDoc的描述:引用队列,在检测到适当的可到达性更改后,垃圾回收器将已注册的引用对象添加到该队列中
查看源代码会发现它很简单,实现了一个队列的入队(enqueue)和出队(poll还有remove)操作,内部元素就是泛型的Reference
,并且Queue
的实现,是由Reference
自身的链表结构所实现的。
再来看 Reference类的代码,注意,javadoc中有一句,提到了它与GC是紧密相关的:
Because reference objects are implemented in close cooperation with the garbage collector, this class may not be subclassed directly.
从数据结构上看,Reference链表结构内部主要的成员有
private T referent; //就是它所指引的
Reference next; //指向下一个;
另一个比较重要的内部数据是:
ReferenceQueue<? super T> queue;
这个queue是通过构造函数传入的,表示创建一个Reference时,要将其注册到那个queue上。
Queue的另一个作用是可以区分不同状态的Reference。Reference有4种状态,不同状态的reference其queue也不同:
Active:
queue = ReferenceQueue with which instance is registered,
or ReferenceQueue.NULL if it was not registered with a queue; next = null.
Pending:
queue = ReferenceQueue with which instance is registered;
next = Following instance in queue, or this if at end of list.
Enqueued:
queue = ReferenceQueue.ENQUEUED; next = Following instance
in queue, or this if at end of list.
Inactive:
queue = ReferenceQueue.NULL; next = this.
那么,当我们创建了一个WeakReference,并且将其referent改变后,究竟发生了什么?先看一段代码:
// eg1
public static void test() throws Exception{
Object o = new Object();
// 默认的构造函数,会使用ReferenceQueue.NULL 作为queue
WeakReference<Object> wr = new WeakReference<Object>(o);
System.out.println(wr.get() == null);
o = null;
System.gc();
System.out.println(wr.get() == null);
}
结果大家都知道,但其内部是怎么实现的,还需重新看Reference的源码,内部有两点需要注意:
1)pending和 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.
*/
private static Reference pending = null;
//这个对象,定义为private,并且全局没有任何给它赋值的地方,
//根据它上面的注释,我们了解到这个变量是和垃圾回收期打交道的。
再看discovered,同样为private,上下文也没有任何地方使用它
transient private Reference<T> discovered; /* used by VM */
//看到了它的注释也明确写着是给VM用的。
上面两个变量对应在VM中的调用,可以参考openjdk中的hotspot源码,在hotspot/src/share/vm/memory/referenceProcessor.cpp 的ReferenceProcessor::discover_reference
方法。(根据此方法的注释由了解到虚拟机在对Reference
的处理有ReferenceBasedDiscovery
和RefeferentBasedDiscovery
两种策略)
2)ReferenceHandler
线程
这个线程在Reference类的static构造块中启动,并且被设置为高优先级和daemon状态。此线程要做的事情,是不断的检查pending 是否为null,如果pending不为null,则将pending进行enqueue,否则线程进入wait状态。
通过这2点,我们来看整个过程:
pending是由jvm来赋值的,当Reference内部的referent对象的可达状态改变时,jvm会将Reference对象放入pending链表。
结合代码eg1中的 o = null;
这一句,它使得o对象满足垃圾回收的条件,并且在后边显式的调用了System.gc()
,垃圾收集进行的时候会标记WeakReference
所referent的对象o为不可达(使得wr.get()==null
),并且通过
赋值给pending
,触发ReferenceHandler
线程处理pending
。
ReferenceHandler
线程要做的是将pending
对象enqueue
,但默认我们所提供的queue,也就是从构造函数传入的是null,实际是使用了ReferenceQueue.NULL
,Handler
线程判断queue为ReferenceQueue.NULL
则不进行操作,只有非ReferenceQueue.NULL
的queue才会将Reference进行enqueue。
ReferenceQueue.NULL
相当于我们提供了一个空的Queue去监听垃圾回收器给我们的反馈,并且对这种反馈不做任何处理。要处理反馈,则必须要提供一个非ReferenceQueue.NULL
的queue。
在WeakHashMap
则在内部提供了一个非NULL的ReferenceQueue
private final ReferenceQueue<K> queue = new ReferenceQueue<K>();
在 WeakHashMap 添加一个元素时,会使用 此queue来做监听器。见put方法中的下面一句:
tab[i] = new Entry<K,V>(k, value, queue, h, e);
这里Entry是一个内部类,继承了WeakReference
class Entry<K,V> extends WeakReference<K> implements Map.Entry<K,V>
WeakHashMap的 put, size, clear
都会间接或直接的调用到 expungeStaleEntries()
方法。
expungeStaleEntries
顾名思义,此方法的作用就是将 queue中陈旧的Reference进行删除,因为其内部的referent都已经不可达了。所以也将这个WeakReference包装的key从map中删除。
个人认为:ReferenceQueue是作为 JVM GC与上层Reference对象管理之间的一个消息传递方式,它使得我们可以对所监听的对象引用可达发生变化时做一些处理,WeakHashMap正是利用此来实现的。用图来大致表示如下:
现在,我们再回到那个帖子的问题:http://www.javaeye.com/topic/587995
他开始的测试写法为:
List<WeakHashMap<byte[][], byte[][]>> maps = new ArrayList<WeakHashMap<byte[][], byte[][]>>();
for (int i = 0; i < 1000; i++) {
WeakHashMap<byte[][], byte[][]> d = new WeakHashMap<byte[][], byte[][]>();
d.put(new byte[1000][1000], new byte[1000][1000]);
maps.add(d);
System.gc();
System.err.println(i);
}
会造成OOM异常。
注意一下,他在for循环里每次都 new 一个新的WeakHashMap,并且key和value都是大对象,之后,他在 for循环的最后增加了一句访问 WeakHashMap的size()
,使得不会造成OOM。
首先上面的代码并不是没有执行GC,而是仅对 WeakHashMap中的key中的byte数组进行了回收,而value依然保持。我们可以先做个试验,把上面的value用小对象代替
for (int i = 0; i < 10000; i++) {
WeakHashMap<byte[][], Object> d = new WeakHashMap<byte[][], Object>();
d.put(new byte[1000][1000], new Object());
maps.add(d);
System.gc();
System.err.println(i);
}
上面的代码,即使执行10000次也没有问题,证明key中的byte数组确实被回收了。
那为何key中的referent的数据被GC,却没有触发WeakHashMap去做清除整个key的操作呢?
因为他for循环中每次都new一个新的WeakHashMap,在put操作后,虽然GC将WeakReference的key中的byte数组回收了,并将事件通知到了ReferenceQueue,但后续却没有相应的动作去触发 WeakHashMap 去处理 ReferenceQueue,所以 WeakReference 包装的key依然存在在WeakHashMap中,其对应的value也当然存在。
而在for循环的尾巴增加了一句 d.size()
方法,却可以了,是因为
size()
里面触发了expungeStaleEntries
操作,它将
ReferenceQueue中的 WeakReference对象从map中删除了,对应着value也一并删除了,使得value也被GC回收了。
话说ReferenceQueue的更多相关文章
- Java Reference & ReferenceQueue一览
Overview The java.lang.ref package provides more flexible types of references than are otherwise ava ...
- java强引用、软引用、弱引用、虚引用
前言概述 在JDK1.2以前的版本中,当一个对象不被任何变量引用,那么程序就无法再使用这个对象.这就像在日常生活中,从商店购买了某样物品后,如果有用,就一直保留它,否则就把它扔到垃圾箱,由清洁工人收走 ...
- 话说C++中的左值、纯右值、将亡值
写在前面 C++中有“左值”.“右值”的概念,C++11以后,又有了“左值”.“纯右值”.“将亡值”的概念.关于这些概念,许多资料上都有介绍,本文在拾人牙慧的基础上又加入了一些自己的一些理解,同时提出 ...
- ReferenceQueue<T>随笔
参考资料: ReferenceQueue食用手册 java引用食用手册 ReferenceQueue源代码里面很好的展示了java队列的实现思路, 以及多线程观察者的实现思路 多线程观察者实现思路: ...
- Java魔法堂:四种引用类型、ReferenceQueue和WeakHashMap
一.前言 JDK1.2以前只提供一种引用类型——强引用 Object obj = new Object(); .而JDK1.2后我们多另外的三个选择分别是软引用 java.lang.ref.SoftR ...
- ReferenceQueue的使用
转:http://www.iflym.com/index.php/java-programe/201407140001.html 1 何为ReferenceQueue 在java的引用体系中,存在着强 ...
- 强、软、弱、虚引用,ReferenceQueue,WeakHashMap
强引用(Reference):所谓强引用就是普通引用.普通引用引用的对象,即使内存不足时,一般情况下也不会被回收. 软引用(weakReference):如果对象被且仅被软引用所引用时,内存不足时,会 ...
- 话说C语言const用法
const在C语言中算是一个比较新的描述符,我们称之为常量修饰符,意即其所修饰 的对象为常量(immutable). 我们来分情况看语法上它该如何被使用. 1.函数体内修饰局部变量. 例: void ...
- 团体程序设计天梯赛-练习集L1-021. 重要的话说三遍
L1-021. 重要的话说三遍 时间限制 400 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 陈越 这道超级简单的题目没有任何输入. 你只需要把这句 ...
- Java对象的强、软、弱和虚引用原理+结合ReferenceQueue对象构造Java对象的高速缓存器
//转 http://blog.csdn.net/lyfi01/article/details/6415726 1.Java对象的强.软.弱和虚引用 在JDK 1.2以前的版本中,若一个对象不被任何变 ...
随机推荐
- 10 Python面向对象编程:类和对象以及和Java的对比
本篇是 Python 系列教程第 10 篇,更多内容敬请访问我的 Python 合集 这里只介绍类和对象,self.属性.方法.访问控制.类继承.方法重写在后面的文章里介绍 在Python中,类和对象 ...
- R-Adapter:零样本模型微调新突破,提升鲁棒性与泛化能力 | ECCV 2024
大规模图像-文本预训练模型实现了零样本分类,并在不同数据分布下提供了一致的准确性.然而,这些模型在下游任务中通常需要微调优化,这会降低对于超出分布范围的数据的泛化能力,并需要大量的计算资源.论文提出新 ...
- C语言linux系统fork函数
References: c语言fork函数 linux中fork()函数详解 一.fork函数简介 作用 在linux下,C语言创建进程用fork函数.fork就是从父进程拷贝一个新的进程出来,子进程 ...
- .net core8 使用Swagger(附当前源码)
说明 该文章是属于OverallAuth2.0系列文章,每周更新一篇该系列文章(从0到1完成系统开发). 该系统文章,我会尽量说的非常详细,做到不管新手.老手都能看懂. 说明:OverallAuth2 ...
- Hash表实践 —— 两数之和
目录 题目背景 解题思路 题目背景 这个题目用常规的双循环就可以完成. 但不是最优解.为什么? 看看他的步骤数: N =[3,2,4] 求结果为6的两个元素坐标如下, 1). 3+2 = 5 不等于 ...
- Identity – 冷知识
RoleManager, RoleStore, EF Core 关系 RoleManager 可以理解为一个上层 service, 是让我们操作 Role 的. 比如 create, update, ...
- SQL Server – 我常用语句
前言 旧没用又忘记了, 又没有 intellisense, 记入这里吧. Reset Auto Increment DBCC CHECKIDENT ('TableName'); -- check cu ...
- Git冲突解决技巧
在多人协作的软件开发项目中,Git 冲突是不可避免的现象.当两个或更多的开发者同时修改了同一段代码,并且尝试将这些修改合并到一起时,冲突就发生了.解决这些冲突是确保代码库健康和项目顺利进行的关键.以下 ...
- C#/.NET/.NET Core技术前沿周刊 | 第 5 期(2024年9.9-9.15)
前言 C#/.NET/.NET Core技术前沿周刊,你的每周技术指南针!记录.追踪C#/.NET/.NET Core领域.生态的每周最新.最实用.最有价值的技术文章.社区动态.优质项目和学习资源等. ...
- C语言实现面向对象方法学的GLib、GObject-初体验
0. 扫盲: GLib是用C写的一些utilities,即C的工具库,和libc/glibc(GNU C Library)没有关系. GLib是 Gtk+ 库和 Gnome 的基础.GLib可以在多个 ...