浏览 4602 次
精华帖 (0) :: 良好帖 (1) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2011-04-13
最后修改:2011-12-06
关于gc和对象可触及性这两块,这里我就不展开了。不过,如果要看懂本文,最好还是先去看下这两块的机制。 本文分三块: 1、jdk引用类介绍(如WeakReference) 2、WeakHashMap工作机制介绍 3、自己实现一个类,让它可以自动回收value的堆区(自认为这是本文亮点) 1、jdk引用类介绍 PS:value的堆区也会自动回收,因为在调用put方法的时候,内部调用了getTable方法,而getTable方法内部又调用了expungeStaleEntries方法,在expungeStaleEntries方法内部处理了已经由gc的后台线程Reference Handler加入到ReferenceQueue<K>中的Reference对象,该对象的内部的value被指向null,从而提示gc未来可回收value原对象的堆区空间了。 本文旨在探讨,如何不调用put/size等方法的时候,value也会自动回收。但,正如我后面的回复所说,本文第三条中的实现方式并不太合理。大家权当是学了下gc对引用的处理罢了。呵呵~ 而jdk里的引用类体系为: 以软引用为例,看下引用的内存结构: 而,对应各种可触及性有很多引用类型 1、强可触及性 2、软可触及性----SoftReference 3、弱可触及性--WeakReference 4、可复活性---FinalReference 5、影子可触及性(译法可不同)---PhantomReference 6、不可触及性 引用类的学习,如果需要详细资料,请参考: 弱引用:http://www.ibm.com/developerworks/cn/java/j-jtp11225/ 软引用:http://www.ibm.com/developerworks/cn/java/j-jtp01246.html 引用类使用指南:https://www.ibm.com/developerworks/cn/java/j-refs/ 《深入Java虚拟机》 尤其前三篇IBM开发社区的文章,分析的相当到位!!! 好了,下面正式开始谈WeakHashMap 2、WeakHashMap工作机制介绍: 这里用简单的话写出,可能不能完全描述清晰,尤其对初学者而言。先这样写着,如果需要,我会专门写一篇WeakHashMap源码分析文章,然后附上链接,那就看大家需不需要了。 在put一个新pair的时候,实际上是将key的引用封装成了一个 WeakReference<K>对象(这不同于HashMap中的直接使用key作为Entry的属性)。于是在每次调用WeakHashMap的 gettable() size() resize() 方法的时候都会调用expungStaleEntries()方法来清除处理已经被gc加入到队列中的弱引用。处理方法见expungStaleEntries方法体: private void expungeStaleEntries() { Entry<K,V> e; while ( (e = (Entry<K,V>) queue.poll()) != null) { int h = e.hash; int i = indexFor(h, table.length); Entry<K,V> prev = table[i]; Entry<K,V> p = prev; while (p != null) { Entry<K,V> next = p.next; if (p == e) { if (prev == e) table[i] = next; else prev.next = next; e.next = null; // Help GC e.value = null; // " " size--; break; } prev = p; p = next; } } } 上面这段代码实际上做的工作是:将已经被加入队列中的弱引用对应的Entry的从整个map的结构中移除,然后断开Entry指向 value的引用,加速gc回收value的堆区空间。而key指向的堆区对象已经在这个引用对象本身被GC加入到queue之前已经被释放了所有所有的弱引用,进入可复活状态或者已经经过可复活状态等待被回收了。 3、自己实现一个类,让它可以自动回收value的堆区 不过,WeakHashMap的设计有一个地方让我们不爽,如果我们没有调用size()方法,value在堆区的空间就不会被释放。于是我写了一个类MyWeakHashMap来替代WeakHashMap: 如果要获得完整代码,请下载附件。 /** * 自己实现的类,替代jdk中的类java.util.WeakHashMap。 * 和WeakHashMap比较,无需调用该java.util.WeakHashMap * 中的size()等方法,该类可以自动释放内部类Entry中的 * value的堆空间。 * @author 贾懂凯 * * @param <K> * @param <V> */ public class MyWeakHashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>{ //如果有兴趣,请下载运行 } 附件中同时有一个测试类Test,通过测试,能很明确地看出我设计的类和jdk自带的WeakHashMap类的区别。 下面给出测试代码和结果: 如果是jdk自带类WeakHashMap: import java.util.ArrayList; import java.util.WeakHashMap; import java.util.List; public class Test { public static void main(String args[]){ //测试1 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); } } } 结果 写道 0
1 …… 63 Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at Test.main(Test.java:18) 我们发先报出了OOM(内存溢出)异常。使用我定义的类,就不会出现OOM: import java.util.WeakHashMap; import java.util.List; public class Test { public static void main(String args[]){ //测试2 List<MyWeakHashMap<byte[][], byte[][]>> maps = new ArrayList<MyWeakHashMap<byte[][], byte[][]>>(); for (int i = 0; i < 1000; i++) { MyWeakHashMap<byte[][], byte[][]> d = new MyWeakHashMap<byte[][], byte[][]>(); d.put(new byte[1000][1000], new byte[1000][1000]); maps.add(d); System.gc(); System.err.println(i); } } } 结果: 写道 0
1 2 3 …… 不会出现OOM(内存溢出)异常。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2011-12-05
这么棒的文章,果断给良好了
|
|
返回顶楼 | |
发表时间:2011-12-05
悲剧了 写道 这么棒的文章,果断给良好了
抱歉了,近期重新看源码才发现,本文关于“value堆区不会自动回收”的论点有误,已在文中用红字说明。 若造成误导,多包含。 |
|
返回顶楼 | |
发表时间:2011-12-06
最后修改:2011-12-06
贾懂凯 写道 悲剧了 写道 这么棒的文章,果断给良好了
抱歉了,近期重新看源码才发现,本文关于“value堆区不会自动回收”的论点有误,已在文中用红字说明。 若造成误导,多包含。 没发现在哪有红字说明 既然是value会回收,那么测试怎么OOM |
|
返回顶楼 | |
发表时间:2011-12-06
贾懂凯 写道 悲剧了 写道 这么棒的文章,果断给良好了
抱歉了,近期重新看源码才发现,本文关于“value堆区不会自动回收”的论点有误,已在文中用红字说明。 若造成误导,多包含。 看了代码理解以下几点: 1.那个expungeStaleEntries();,不只size()才会调用 如下: private Entry[] getTable() { expungeStaleEntries(); return table; } /** * Returns the number of key-value mappings in this map. * This result is a snapshot, and may not reflect unprocessed * entries that will be removed before next attempted access * because they are no longer referenced. */ public int size() { if (size == 0) return 0; expungeStaleEntries(); return size; } Entry(K key, V value, ReferenceQueue<K> queue, int hash, Entry<K,V> next) { super(key, queue); this.value = value; this.hash = hash; this.next = next; } 调用的super是: public WeakReference(T referent, ReferenceQueue<? super T> q) { super(referent, q); } 证明key最开始就是弱引用,value是强引用 value随着key被清除,expungeStaleEntries()调用,value变成了弱引用,然后gc()就没了 那么expungeStaleEntries()里根据key已经被清除了,才会执行--value变成弱引用 如下: private void expungeStaleEntries() { Entry<K,V> e; while ( (e = (Entry<K,V>) queue.poll()) != null) { for ( ooo=queue.poll() ;ooo != null;) { if(ooo instanceof Entry){ e=(Entry<K,V>)ooo; }else{ continue; } 代码存在问题,queue.poll()一直是Entrt实例 for循环一直进行,就是为了不听的监听是否弱键被释放了,这个性能也太耗了点 |
|
返回顶楼 | |
发表时间:2011-12-06
悲剧了 写道 没发现在哪有红字说明 已在博文中修改,但论坛的文本并没有变。可能是浏览器缓存或者javaeye服务器有多份copy没有更新的问题。 悲剧了 写道 既然是value会回收,那么测试怎么OOM 原来的测试,是假设没有调用put/size等包含expungeStaleEntries等方法的时候,虽然key指向的堆区被gc回收,但value的堆区还存在强引用(Reference还在),故不会回收,我写的MyWeakHashMap的目的是,保证只要key的堆区被回收,同时value的堆区同时也会被回收,无需再调用put/size等方法。 故,原来的测试结果还是成立的! 悲剧了 写道 for ( ooo=queue.poll() ;ooo != null;) { if(ooo instanceof Entry){ e=(Entry<K,V>)ooo; }else{ continue; } 代码存在问题,queue.poll()一直是Entrt实例 for循环一直进行,就是为了不听的监听是否弱键被释放了,这个性能也太耗了点 不会一直循环,queue中保存的只是被gc加入的Entry实例,而gc判断假如的标准是Entry对象是否是弱引用。所以处理完所有的弱引用,就退出循环了。 另外,关于我的MyHashMap的实现,思路很简单:只是将原来在Entry中强引用保存value: private V value; 换成弱引用保存: private ValueEntry<V> value; //ValueEntry定义为内部类: private static class ValueEntry<V> extends WeakReference<V>{ public ValueEntry(V referent, ReferenceQueue<? super V> q) { super(referent, q); } } 现在我否定这个方案的原因是:如果外部不存在对value的强引用,但存在对key的强引用,value的堆区空间也会被回收,这不合理! |
|
返回顶楼 | |
发表时间:2011-12-07
贾懂凯 写道 for ( ooo=queue.poll() ;ooo != null;) { if(ooo instanceof Entry){ e=(Entry<K,V>)ooo; }else{ continue; } 代码存在问题,queue.poll()一直是Entrt实例 for循环一直进行,就是为了不听的监听是否弱键被释放了,这个性能也太耗了点 不会一直循环,queue中保存的只是被gc加入的Entry实例,而gc判断假如的标准是Entry对象是否是弱引用。所以处理完所有的弱引用,就退出循环了。 看这个代码的时候先入为主了,不好意思。 一直想着怎么能做到value不需要调用那个方法就能自动回收,一见到for里面的判断就直接跳转到while(true){}这种时刻监听,然后根据队列中是否有value值来把强引用变成弱引用。 贾懂凯 写道 另外,关于我的MyHashMap的实现,思路很简单:只是将原来在Entry中强引用保存value: private V value; 换成弱引用保存: private ValueEntry<V> value; //ValueEntry定义为内部类: private static class ValueEntry<V> extends WeakReference<V>{ public ValueEntry(V referent, ReferenceQueue<? super V> q) { super(referent, q); } } 现在我否定这个方案的原因是:如果外部不存在对value的强引用,但存在对key的强引用,value的堆区空间也会被回收,这不合理! 那么基于这一点,要实现value自动释放只有定时调用expungeStaleEntries,内部自己重写map,外部自己做任务。 |
|
返回顶楼 | |