论坛首页 Java企业应用论坛

引用类,WeakHashMap,以及让value自动回收

浏览 4602 次
精华帖 (0) :: 良好帖 (1) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2011-04-13   最后修改:2011-12-06
   如果要彻底明白WeakHashMap这个类,需要联系GC和对象的可触及状态(强可触及、软可触及……)来看,可参考JVM规范里相关内容。
关于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(内存溢出)异常。






  • 大小: 38.2 KB
  • 大小: 13.1 KB
   发表时间:2011-12-05  
这么棒的文章,果断给良好了
0 请登录后投票
   发表时间:2011-12-05  
悲剧了 写道
这么棒的文章,果断给良好了

抱歉了,近期重新看源码才发现,本文关于“value堆区不会自动回收”的论点有误,已在文中用红字说明。
若造成误导,多包含。
0 请登录后投票
   发表时间:2011-12-06   最后修改:2011-12-06
贾懂凯 写道
悲剧了 写道
这么棒的文章,果断给良好了

抱歉了,近期重新看源码才发现,本文关于“value堆区不会自动回收”的论点有误,已在文中用红字说明。
若造成误导,多包含。



没发现在哪有红字说明

既然是value会回收,那么测试怎么OOM
0 请登录后投票
   发表时间: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循环一直进行,就是为了不听的监听是否弱键被释放了,这个性能也太耗了点

0 请登录后投票
   发表时间: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的堆区空间也会被回收,这不合理!
0 请登录后投票
   发表时间: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,外部自己做任务。
0 请登录后投票
论坛首页 Java企业应用版

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