论坛首页 Java企业应用论坛

WeakHashMap的神话

浏览 28560 次
精华帖 (0) :: 良好帖 (17) :: 新手帖 (14) :: 隐藏帖 (0)
作者 正文
   发表时间:2010-02-08  
nishizhutoua 写道
这种情况下,可以尝试把存Map的list也设置为弱引用.



从证明楼主要表达的主题的角度来说,楼主的示例代码挺合适的。
0 请登录后投票
   发表时间:2010-02-08  
楼主运行你第一个会出OOM的例子,将其中的System.err.println(i)改为System.err.println(i+"   weakhashmap size="+d.size());
因为是在gc后,每次查看d.size的情况,,

在gc以前,for里的每次执行都没有去访问d,但是可以看到很多时候size都是0,说明已经被自动清除了,个别时候是1,?好像和你的说法有冲突啊

0   weakhashmap size=0
1   weakhashmap size=0
2   weakhashmap size=0
3   weakhashmap size=1
4   weakhashmap size=0
5   weakhashmap size=0
6   weakhashmap size=0
7   weakhashmap size=0
8   weakhashmap size=0
0 请登录后投票
   发表时间:2010-02-09  
yangkai1217 写道
楼主运行你第一个会出OOM的例子,将其中的System.err.println(i)改为System.err.println(i+"   weakhashmap size="+d.size());
因为是在gc后,每次查看d.size的情况,,

在gc以前,for里的每次执行都没有去访问d,但是可以看到很多时候size都是0,说明已经被自动清除了,个别时候是1,?好像和你的说法有冲突啊

0   weakhashmap size=0
1   weakhashmap size=0
2   weakhashmap size=0
3   weakhashmap size=1
4   weakhashmap size=0
5   weakhashmap size=0
6   weakhashmap size=0
7   weakhashmap size=0
8   weakhashmap size=0


请看JDK中WeakHashMap的size方法的实现:
    
public int size() {
        if (size == 0)
            return 0;
        expungeStaleEntries();
        return size;
    }


在size返回之前,先去清除已经失去了KEY的ENTRY。至于为什么有的时候是0有的时候是1,请看
joachimz 写道
2 WeakReference也不是一有gc就会消失

0 请登录后投票
   发表时间:2010-02-11  
WeakHashMap本就不是即时的清除包装。需要在操作时清除。
不然我也不会写自己的引用类型Map
0 请登录后投票
   发表时间:2010-02-27  
Agrael 写道
WeakHashMap本就不是即时的清除包装。需要在操作时清除。
不然我也不会写自己的引用类型Map

敢问你的引用类型map是如何实现的
能够做到即使不调用map的方法也能在垃圾回收的时候自动释放entry?
0 请登录后投票
   发表时间:2010-10-18   最后修改:2010-10-18
Hi。。

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.out.println(Runtime.getRuntime().freeMemory()/1024); // 观察可用内存大小的变化
            //System.err.println("iterator "+i +": "+maps.get(i).size());
        }

    }

稍微修改程序同样会出现outofmemory exception , 然后在System.gc()后面添加
Thread.sleep(1000);

sleep时间越长越不会抛出内存溢出的异常。。
为什么会出现这种情况,分析如下:
考虑两个线程:
1,gc线程;
2,上面程序所在的当前线程。
两线程竞争这CPU的使用。
不加“Thread.sleep(1000);”时,gc“竞争”不错当前线程,所以会出现 gc线程来不及释放无用的对象,导致outofmemory异常。
加“Thread.sleep(1000);”时,gc线程有充分的CPU去回收weakreference所指的对象。

楼主的观点:
引用
WeakHashMap 在你访问它的内容的时候释放内部不用的对象
 
严格来讲:准确来说应该是在gc回收后,才释放不用的对象。当操作WeakHashMap内容(比如size,put,get等)的时候,才清除已经被gc掉的对象所对应的Entry,变化是对象WeakHashMap,内存情况并没有变化,所以说不上“WeakHashMap 在你访问它的内容的时候释放不用的对象”。可以理解楼主在措辞方面的不当。

如上描述有误,跟贴。
0 请登录后投票
   发表时间:2010-12-10  
Anddy 写道
Hi。。

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.out.println(Runtime.getRuntime().freeMemory()/1024); // 观察可用内存大小的变化
            //System.err.println("iterator "+i +": "+maps.get(i).size());
        }

    }

稍微修改程序同样会出现outofmemory exception , 然后在System.gc()后面添加
Thread.sleep(1000);

sleep时间越长越不会抛出内存溢出的异常。。
为什么会出现这种情况,分析如下:
考虑两个线程:
1,gc线程;
2,上面程序所在的当前线程。
两线程竞争这CPU的使用。
不加“Thread.sleep(1000);”时,gc“竞争”不错当前线程,所以会出现 gc线程来不及释放无用的对象,导致outofmemory异常。
加“Thread.sleep(1000);”时,gc线程有充分的CPU去回收weakreference所指的对象。

楼主的观点:
引用
WeakHashMap 在你访问它的内容的时候释放内部不用的对象
 
严格来讲:准确来说应该是在gc回收后,才释放不用的对象。当操作WeakHashMap内容(比如size,put,get等)的时候,才清除已经被gc掉的对象所对应的Entry,变化是对象WeakHashMap,内存情况并没有变化,所以说不上“WeakHashMap 在你访问它的内容的时候释放不用的对象”。可以理解楼主在措辞方面的不当。

如上描述有误,跟贴。

真怀疑你自己有没有试过你自己写的代码。

按照你的程序,并不是sleep时间越长越不会抛出内存溢出的异常,而是sleep时间越长越晚抛出内存溢出的异常。

  • 大小: 474.9 KB
0 请登录后投票
   发表时间:2010-12-10  
楼主看的是还挺细的,是一个好的问题。 值得投个良好
0 请登录后投票
   发表时间:2010-12-10  
找我个人的经验来说,WeakHashMap只有对此map发生操作时(修改,size),把过期的对象加入回收序列,直到GC回收了序列中的东西才释放内存.
不操作map,或者GC运行了但是还没回收这些都不算真的释放.
0 请登录后投票
   发表时间:2010-12-10   最后修改:2010-12-10
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研究精神可嘉,不做评价!

0 请登录后投票
论坛首页 Java企业应用版

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