论坛首页 Java企业应用论坛

WeakHashMap的神话

浏览 28559 次
精华帖 (0) :: 良好帖 (17) :: 新手帖 (14) :: 隐藏帖 (0)
作者 正文
   发表时间:2010-02-05  

在广大的Java界,关于WeakHashMap一直都存在这么个传说:

 

在WeakHashMap 中,当某个键不再正常使用时,将自动移除其条目

 可是WeakHashMap是真的自动移除其条目吗?

 

今天因为闲来无事,所以想看看WeakHashMap是如何自动实现移除其内部不用的条目从而达到的自动释放内存的目的的。仔细的看了看JVM自带的源代码的实现,在WeakHashMap是主要通过expungeStaleEntries这个函数的来实现的。基本上只要对WeakHashMap的内容进行访问就会调用这个函数,从而达到清除其内部不在为外部引用的条目。但是如果预先生成了WeakHashMap,而在GC以前又不曾访问该WeakHashMap,那不是就不能释放内存了吗?

 

写个代码测试一把:

	public static void main(String[] args) throws Exception {

		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);


		}

	}

 

由于Java默认内存是64M,所以再不改变内存参数的情况下,该测试跑不了几步循环就内存溢出了。果不其然,WeakHashMap这个时候并没有自动帮我们释放不用的内存。

 

再加个对会对map进行访问的测试试试:

public static void main(String[] args) throws Exception {

		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);

			for (int j = 0; j < i; j++) {
				System.err.println(j+  " size" + maps.get(j).size());
			}
		}
	}

 

这下测试就顺利通过了。

 

总结来说:WeakHashMap并不是你啥也干他就能自动释放内部不用的对象的,而是在你访问它的内容的时候释放内部不用的对象。这两句话看似区别不大,但是有时候一个小小的区别就会要了命的。

   发表时间:2010-02-07  
本想投新手贴了事,版主读书还是不仔细啊,至少犯几个错误:
1 System.gc并不一定会马上gc
2 WeakReference也不是一有gc就会消失
3 WeakHashMap中,只有值对象可能被回收
4 被回收后,只是获得了一个null并非Map的 Entry也被回收,或者说Map size并不减少

0 请登录后投票
   发表时间:2010-02-07  
joachimz 写道
本想投新手贴了事,版主读书还是不仔细啊,至少犯几个错误:


应该称搂主吧?
0 请登录后投票
   发表时间:2010-02-07  
joachimz 写道
本想投新手贴了事,版主读书还是不仔细啊,至少犯几个错误:
1 System.gc并不一定会马上gc
2 WeakReference也不是一有gc就会消失
3 WeakHashMap中,只有值对象可能被回收
4 被回收后,只是获得了一个null并非Map的 Entry也被回收,或者说Map size并不减少


估计你根本没看清楚楼主在说什么。
假设每个Value为1M,楼主说的不是往1个WeakHashMap中塞N个Value的情况 ,而是往N个WeakHapMap中各塞一个Value的情况。
由于WeakHashMap是在有操作的情况下才会去执行expungeStaleEntries(),将entry中对value的引用释放。如果作了N个WeakHaspMap出去又不touch它们,确实是会抛OOM。
1 请登录后投票
   发表时间:2010-02-07  
iamlotus 写道
joachimz 写道
本想投新手贴了事,版主读书还是不仔细啊,至少犯几个错误:
1 System.gc并不一定会马上gc
2 WeakReference也不是一有gc就会消失
3 WeakHashMap中,只有值对象可能被回收
4 被回收后,只是获得了一个null并非Map的 Entry也被回收,或者说Map size并不减少


估计你根本没看清楚楼主在说什么。
假设每个Value为1M,楼主说的不是往1个WeakHashMap中塞N个Value的情况 ,而是往N个WeakHapMap中各塞一个Value的情况。
由于WeakHashMap是在有操作的情况下才会去执行expungeStaleEntries(),将entry中对value的引用释放。如果作了N个WeakHaspMap出去又不touch它们,确实是会抛OOM。



楼上正解。

我并不想给大家普及System.gc、WeakReference、WeakHashMap的用法,只是想指出WeakHashMap的用处中的一个需要注意的地方。
0 请登录后投票
   发表时间:2010-02-07  
joachimz 写道
本想投新手贴了事,版主读书还是不仔细啊,至少犯几个错误:
1 System.gc并不一定会马上gc
2 WeakReference也不是一有gc就会消失
3 WeakHashMap中,只有值对象可能被回收
4 被回收后,只是获得了一个null并非Map的 Entry也被回收,或者说Map size并不减少




1、2是对的,3、4却是错了。

正确与否,请试后再说。如果你又兴趣,请自己看看expungeStaleEntries的实现,WeakHashMap是直接把ENTRY从Map中去掉,并非像WeakReference一样留下一个NULL让你自己判断。

另Entry是按照键去判断是否该从map中去掉该ENTRY的,并不是回收值对象:
private static class Entry<K,V> extends WeakReference<K> implements Map.Entry<K,V> {
0 请登录后投票
   发表时间:2010-02-07   最后修改:2010-02-07
iamlotus 写道
joachimz 写道
本想投新手贴了事,版主读书还是不仔细啊,至少犯几个错误:
1 System.gc并不一定会马上gc
2 WeakReference也不是一有gc就会消失
3 WeakHashMap中,只有值对象可能被回收
4 被回收后,只是获得了一个null并非Map的 Entry也被回收,或者说Map size并不减少


估计你根本没看清楚楼主在说什么。
假设每个Value为1M,楼主说的不是往1个WeakHashMap中塞N个Value的情况 ,而是往N个WeakHapMap中各塞一个Value的情况。
由于WeakHashMap是在有操作的情况下才会去执行expungeStaleEntries(),将entry中对value的引用释放。如果作了N个WeakHaspMap出去又不touch它们,确实是会抛OOM。


应该是对entry都释放——在API文档中描述如下:
引用
An entry in a WeakHashMap will automatically be removed when its key is no longer in ordinary use.

楼主给的第二个例子打出的size明明是“0”嘛。

对楼主的第二个例子稍做修改,如下:
public static void main(String[] args) throws Exception {  
		  
	    List<WeakHashMap<Object, byte[][]>> maps = new ArrayList<WeakHashMap<Object, byte[][]>>();  
	    Object o = new Object();
	    for (int i = 0; i < 1000; i++) {  
	        WeakHashMap<Object, byte[][]> d = new WeakHashMap<Object, byte[][]>();  
	        d.put(o, new byte[1000][1000]);  
	        maps.add(d);  
	        System.gc();  
	        System.err.println(i);  
	  
	        for (int j = 0; j < i; j++) {  
                System.err.println(j+  " size  " + maps.get(j).size());  
            }  
	    }  
	  
	}

运行后,结果跟第一个例子一样——内存不够了。
0 请登录后投票
   发表时间:2010-02-08   最后修改:2010-02-08
这种情况下,可以尝试把存Map的list也设置为弱引用.


0 请登录后投票
   发表时间:2010-02-08  
应给个良好的,不管怎么说,楼主提出了一个有意义的命题,虽然部分观点没有全都正确。
楼上的例子肯定溢出咯,key一直被你引用着,说明不了楼主要说明的问题。
0 请登录后投票
   发表时间:2010-02-08  
linliangyi2007 写道
应给个良好的,不管怎么说,楼主提出了一个有意义的命题,虽然部分观点没有全都正确。
楼上的例子肯定溢出咯,key一直被你引用着,说明不了楼主要说明的问题。


嗯,我是想举例说明他们说的不移除key只移除value的说法是错的。
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics