论坛首页 Java企业应用论坛

WeakHashMap的神话

浏览 28461 次
精华帖 (0) :: 良好帖 (17) :: 新手帖 (14) :: 隐藏帖 (0)
作者 正文
   发表时间:2010-12-10  
mercyblitz 写道
mikab 写道

在广大的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并不是你啥也干他就能自动释放内部不用的对象的,而是在你访问它的内容的时候释放内部不用的对象。这两句话看似区别不大,但是有时候一个小小的区别就会要了命的。

 

楼上很多人没有答到点子上面。

 

首先,请允许我把你的程序修改一下:

 

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

		//List<WeakHashMap<byte[][], byte[][]>> maps = new ArrayList<WeakHashMap<byte[][], byte[][]>>();
		WeakHashMap<byte[][], byte[][]> d = new WeakHashMap<byte[][], byte[][]>();
		for (int i = 0; i < 1000; i++) {
			d.put(new byte[1000][1000], new byte[1000][1000]);
			//maps.add(d);
			System.gc();
			System.err.println(i);
		}

 

List<WeakHashMap<byte[][], byte[][]>> maps = new ArrayList<WeakHashMap<byte[][], byte[][]>>();

 

List对象强引用,它不会释放WeakHashMap对象。要知道你的程序有一个致命的缺点在:

 

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

 

在循环中,每个新的WeakHashMap在调用put方法中,

public V put(K key, V value) {
        K k = (K) maskNull(key);
        int h = HashMap.hash(k.hashCode());
        Entry[] tab = getTable();
        int i = indexFor(h, tab.length);
......

 而getTable方法中,

 private Entry[] getTable() {
        expungeStaleEntries();
        return table;
 }

 

因为每个新的WeakHashMap实例,刚开始是空Map,不可能肮脏的数据,因此不存在回收的问题。换句话说,这里保存的都是强引用(暂时的),即使后来成为了“弱引用”对象的话,但是每个WeakHashMap实例,仅仅调用了一次put,没有第二次,所以不会判断前面的Entity弱引用对象。

 

WeakHashMap不是全能的,是因为每次put的时候,虽然判断那些对象是“弱引用”的对象,这些“弱引用”的对象是不定的情况下,下个GC阶段何时执行不确定。

 

从API层次来说,这个设计还行,但是引用淘汰入队则是由JVM负责。

 

PS:LZ研究精神可嘉,不做评价!

 

 

补充一点:

之所以调用 System.gc()(抛开gc方法的特殊情况),就是JVM在GC时,代码中的字节数组还是没有被释放。因为他们是弱引用,注意还是在引用,还没有被清扫掉。因此OOM是必须的。

 

 

0 请登录后投票
   发表时间:2010-12-10  
WeakHashMap实现弱引用,是因为它的Entry<K,V>是继承自WeakReference<K>的

在WeakHashMap$Entry<K,V>的构造函数里面是这样写的:

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(key, queue);”,传入的是key,因此key才是进行弱引用的,value是直接强引用关联在this.value之中,在System.gc()时,key被清除了,WeakHashMap本身根据ReferenceQueue中接收到的清除通知来清理value值,这个动作实现在expungeStaleEntries()方法之内,在getTable()之中对这个方法进行了调用,而WeakHashMap几乎在所有public的方法中都是要调用getTable()的。所以效果是key在GC的时候被清楚,value在key清除后访问WeakHashMap的时候被清除。

WeakHashMap的说明之中也是说“An entry in a WeakHashMap will automatically be removed when its key is no longer in ordinary use”。所以WeakHashMap的实现并没有问题,只是人们大多想当然的理解为value会自动清除而已。
1 请登录后投票
   发表时间:2010-12-10  
IcyFenix 写道
WeakHashMap实现弱引用,是因为它的Entry<K,V>是继承自WeakReference<K>的

在WeakHashMap$Entry<K,V>的构造函数里面是这样写的:

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(key, queue);”,传入的是key,因此key才是进行弱引用的,value是直接强引用关联在this.value之中,在System.gc()时,key被清除了,WeakHashMap本身根据ReferenceQueue中接收到的清除通知来清理value值,这个动作实现在expungeStaleEntries()方法之内,在getTable()之中对这个方法进行了调用,而WeakHashMap几乎在所有public的方法中都是要调用getTable()的。所以效果是key在GC的时候被清楚,value在key清除后访问WeakHashMap的时候被清除。

WeakHashMap的说明之中也是说“An entry in a WeakHashMap will automatically be removed when its key is no longer in ordinary use”。所以WeakHashMap的实现并没有问题,只是人们大多想当然的理解为value会自动清除而已。


嗯,不过不是被清除了,而是被标记成了弱引用,在下次调用expungeStaleEntries()方法时,Value才被GC掉,伴随着entry被清除时!
0 请登录后投票
论坛首页 Java企业应用版

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