话说ReferenceQueue
2016-04-09 11:40
681 查看
也是几年前写的,在内部邮件列表里发过,在这里保存一下。
看到了这篇帖子: 《WeakHashMap的神话》http://www.javaeye.com/topic/587995
因为Javaeye回帖还要先做个论坛小测验,所以懒得在上面回复了,在这里说下。
以前设计缓存时也曾过用WeakHashMap来实现,对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类的代码,注意,javadoc中有一句,提到了它与GC是紧密相关的:
Because reference objects are implemented in close cooperation with the garbage collector, this class may not be subclassed directly.
从数据结构上看,Reference链表结构内部主要的成员有
另一个比较重要的内部数据是:
这个queue是通过构造函数传入的,表示创建一个Reference时,要将其注册到那个queue上。
Queue的另一个作用是可以区分不同状态的Reference。Reference有4种状态,不同状态的reference其queue也不同:
Active:
Pending:
Enqueued:
Inactive:
那么,当我们创建了一个WeakReference,并且将其referent改变后,究竟发生了什么?先看一段代码:
结果大家都知道,但其内部是怎么实现的,还需重新看Reference的源码,内部有两点需要注意:
1)pending和 discovered成员:
先看pending对象
再看discovered,同样为private,上下文也没有任何地方使用它
上面两个变量对应在VM中的调用,可以参考openjdk中的hotspot源码,在hotspot/src/share/vm/memory/referenceProcessor.cpp 的
2)
这个线程在Reference类的static构造块中启动,并且被设置为高优先级和daemon状态。此线程要做的事情,是不断的检查pending 是否为null,如果pending不为null,则将pending进行enqueue,否则线程进入wait状态。
通过这2点,我们来看整个过程:
pending是由jvm来赋值的,当Reference内部的referent对象的可达状态改变时,jvm会将Reference对象放入pending链表。
结合代码eg1中的
赋值给
在
在 WeakHashMap 添加一个元素时,会使用 此queue来做监听器。见put方法中的下面一句:
这里Entry是一个内部类,继承了WeakReference
WeakHashMap的
个人认为:ReferenceQueue是作为 JVM GC与上层Reference对象管理之间的一个消息传递方式,它使得我们可以对所监听的对象引用可达发生变化时做一些处理,WeakHashMap正是利用此来实现的。用图来大致表示如下:
现在,我们再回到那个帖子的问题:http://www.javaeye.com/topic/587995
他开始的测试写法为:
会造成OOM异常。
注意一下,他在for循环里每次都 new 一个新的WeakHashMap,并且key和value都是大对象,之后,他在 for循环的最后增加了一句访问
首先上面的代码并不是没有执行GC,而是仅对 WeakHashMap中的key中的byte数组进行了回收,而value依然保持。我们可以先做个试验,把上面的value用小对象代替
上面的代码,即使执行10000次也没有问题,证明key中的byte数组确实被回收了。
那为何key中的referent的数据被GC,却没有触发WeakHashMap去做清除整个key的操作呢?
因为他for循环中每次都new一个新的WeakHashMap,在put操作后,虽然GC将WeakReference的key中的byte数组回收了,并将事件通知到了ReferenceQueue,但后续却没有相应的动作去触发 WeakHashMap 去处理 ReferenceQueue,所以 WeakReference 包装的key依然存在在WeakHashMap中,其对应的value也当然存在。
而在for循环的尾巴增加了一句
ReferenceQueue中的 WeakReference对象从map中删除了,对应着value也一并删除了,使得value也被GC回收了。
看到了这篇帖子: 《WeakHashMap的神话》http://www.javaeye.com/topic/587995
因为Javaeye回帖还要先做个论坛小测验,所以懒得在上面回复了,在这里说下。
以前设计缓存时也曾过用WeakHashMap来实现,对Java的
Reference稍做过一些了解,其实这个问题,归根到底,是个
Java GC的问题,由垃圾回收器与
ReferenceQueue的交互方式决定的。
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回收了。
相关文章推荐
- 获取用户最后点击视图(UIWindow的封装)
- 关于从图库选择图片的问题,Android4.4前后的改动,顺便吐槽小米MIUI
- Windows下C#的GUI窗口程序中实现调用Google Map的实例
- response、request的使用
- Android Handler+ExecutorService(线程池)+MessageQueue模式+缓存模式机制
- UI之网路请求(进度条小知识)
- IOS 开发-UI初级 (三)控件:UISwitch, UIActivityIndicatorView,UISlider,UISegmentedControl
- poj 2524 Ubiquitous Religions
- Mac 安装出现emulator: ERROR: x86 emulation currently requires hardware acceleration! Please ensure Intel HAXM is properly installed and usable. CPU acceleration status: HAX kernel modu
- Servlet Request
- 山东省第四届ACM省赛题——Rescue The Princess(计算几何)
- String StringBuffer StringBuilder
- java读取excel的时候,使用getRichStringCellValue()读取到数值类型数据的时候就报错了
- 3——Building Microservices: Inter-Process Communication in a Microservices Architecture
- 2——Building Microservices: Using an API Gateway
- 山东省第四届ACM大学生程序设计竞赛 Rescue The Princess
- 利用google test 与QTest进行GUI 测试
- 旋转轮子 UIActivityIndicatorView
- UIAlertView 与 UIActionSheet (提示用户)的使用方法
- EDKII Build Process:EDKII项目源码的配置、编译流程[三]