WeakHashMap和HashMap的区别

本文详细解析了Java WeakHashMap的特性,包括它如何通过弱引用机制自动释放内存,以及在不同场景下的应用与限制。文章通过实例演示了WeakHashMap与普通HashMap的区别,特别是当对象被垃圾回收时,WeakHashMap如何处理这些对象。此外,文章还讨论了如何避免WeakHashMap中形成循环引用的问题,以及在使用时需要注意的几点注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

WeakHashMap,此种Map的特点是,当除了自身有对key的引用外,此key没有其他引用那么此map会自动丢弃此值,

见实例:此例子中声明了两个Map对象,一个是HashMap,一个是WeakHashMap,同时向两个map中放入a、b两个对象,当HashMap remove掉a 并且将a、b都指向null时,WeakHashMap中的a将自动被回收掉。出现这个状况的原因是,对于a对象而言,当HashMap remove掉并且将a指向null后,除了WeakHashMap中还保存a外已经没有指向a的指针了,所以WeakHashMap会自动舍弃掉a,而对于b对象虽然指向了null,但HashMap中还有指向b的指针,所以

WeakHashMap将会保留

Java代码 收藏代码
  1. packagetest;
  2. importjava.util.HashMap;
  3. importjava.util.Iterator;
  4. importjava.util.Map;
  5. importjava.util.WeakHashMap;
  6. publicclassTest{
  7. publicstaticvoidmain(String[]args)throwsException{
  8. Stringa=newString("a");
  9. Stringb=newString("b");
  10. Mapweakmap=newWeakHashMap();
  11. Mapmap=newHashMap();
  12. map.put(a,"aaa");
  13. map.put(b,"bbb");
  14. weakmap.put(a,"aaa");
  15. weakmap.put(b,"bbb");
  16. map.remove(a);
  17. a=null;
  18. b=null;
  19. System.gc();
  20. Iteratori=map.entrySet().iterator();
  21. while(i.hasNext()){
  22. Map.Entryen=(Map.Entry)i.next();
  23. System.out.println("map:"+en.getKey()+":"+en.getValue());
  24. }
  25. Iteratorj=weakmap.entrySet().iterator();
  26. while(j.hasNext()){
  27. Map.Entryen=(Map.Entry)j.next();
  28. System.out.println("weakmap:"+en.getKey()+":"+en.getValue());
  29. }
  30. }
  31. }

来自:

http://mzlly999.iteye.com/blog/1126049

==================================================

先把问题说清楚:

WeakHashMap是主要通过expungeStaleEntries这个函数的来实现移除其内部不用的条目从而达到的自动释放内存的目的的.基本上只要对WeakHashMap的内容进行访问就会调用这个函数,从而达到清除其内部不在为外部引用的条目。但是如果预先生成了WeakHashMap,而在GC以前又不曾访问该WeakHashMap,那不是就不能释放内存了吗?

对应的两个测试案例:

WeakHashMapTest1:

public class WeakHashMapTest1 {
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这个时候并没有自动帮我们释放不用的内存。

WeakHashMapTest2:

public class WeakHashMapTest2 {
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并不是你啥也干他就能自动释放内部不用的对象的,而是在你访问它的内容的时候释放内部不用的对象

问题讲清楚了,现在我们来梳理一下.了解清楚其中的奥秘.

WeakHashMap实现弱引用,是因为它的Entry<K,V>是继承自WeakReference<K>

WeakHashMap$Entry<K,V>的类定义及构造函数里面是这样写的

private static class Entry<K,V> 
extends WeakReference<K>
implements Map.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中的byte数组进行了回收,value依然保持(value被强关联到entry,entry又关联在map,map关联在arrayList.).

如何证明key中的byte被回收了呢?可以通过内存溢出时导出的内存镜像进行分析,也可以通过如下的小测试得出结论:

把上面的value用小对象代替

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

上面的代码,即使执行10000次也没有问题,证明key中的byte数组确实被回收了。

for循环中每次都new一个新的WeakHashMap,在put操作后,虽然GC将WeakReference的key中的byte数组回收了,并将事件通知到了ReferenceQueue,但后续却没有相应的动作去触发 WeakHashMap 去处理 ReferenceQueue

所以 WeakReference 包装的key依然存在在WeakHashMap中,其对应的value也当然存在。

value是何时被清除的呢?

对两个例子进行分析可知,例子二中的maps.get(j).size()触发了value的回收,那又如何触发的呢.查看WeakHashMap源码可知,size方法调用了expungeStaleEntries方法,该方法对vm要回收的的entry(quene)进行遍历,并将entryvalue置空,回收了内存.

所以效果是keyGC的时候被清除,valuekey清除后访问WeakHashMap被清除.

疑问:keyquenemapquene是同一个quene,poll操作会减少一个reference,那问题是key如果先被清除,expungeStaleEntries遍历quene时那个被回收的key对应的entry还能取出来么???

关于执行System.GC,key中的byte数据如何被回收了,请见WeakReference referenceQuene

来这里:http://www.cnblogs.com/redcreen/archive/2011/02/15/1955289.html

WeakHashMap
<wbr style="line-height:25px">publicclass<span style="color:#993300; line-height:25px">WeakHashMap</span>&lt;K,V&gt;<br style="line-height:25px"> extendsAbstractMap&lt;K,V&gt;<br style="line-height:25px"> implementsMap&lt;K,V&gt;<br style="line-height:25px"><wbr style="line-height:25px"><span style="color:#003366; line-height:25px">以弱键实现的基于哈希表的Map。在WeakHashMap中,当某个键不再正常使用时,将自动移除其条目。</span><wbr style="line-height:25px"><br style="line-height:25px"><span style="color:#000080; line-height:25px">更精确地说,对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。<br style="line-height:25px"> 丢弃某个键时,其条目从映射中有效地移除,因此,该类的行为与其他的Map实现有所不同。<br style="line-height:25px"></span>null值和null键都被支持。该类具有与HashMap类相似的性能特征,并具有相同的效能参数初始容量和加载因子。<br style="line-height:25px"><span style="color:#000080; line-height:25px"><wbr style="line-height:25px">像大多数集合类一样,该类是不同步的。可以使用Collections.synchronizedMap方法来构造同步的WeakHashMap<wbr style="line-height:25px">。</wbr></wbr></span><br style="line-height:25px"> 该类主要与这样的键对象一起使用,<span style="color:#000080; line-height:25px">其equals方法使用</span><span style="color:#ff6600; line-height:25px">==</span><span style="color:#000080; line-height:25px">运算符来测试对象标识。</span><br style="line-height:25px"> 一旦这种键被丢弃,就永远无法再创建了,所以,过段时间后在WeakHashMap中查找此键是不可能的,不必对其项已移除而感到惊讶。<br style="line-height:25px"><span style="color:#000080; line-height:25px">该类十分适合与equals方法不是基于对象标识的键对象一起使用,比如,String实例。</span><br style="line-height:25px"> 然而,对于这种可重新创建的键对象,键若丢弃,就自动移除WeakHashMap条目,这种表现令人疑惑。<br style="line-height:25px"><span style="color:#000080; line-height:25px">WeakHashMap类的行为部分取决于垃圾回收器的动作</span>,所以,几个常见的(虽然不是必需的)Map常量不支持此类。<br style="line-height:25px"><span style="color:#000080; line-height:25px">因为垃圾回收器在任何时候都可能丢弃键,WeakHashMap就像是一个被悄悄移除条目的未知线程</span>。<br style="line-height:25px"> 特别地,即使对WeakHashMap实例进行同步,并且没有调用任何赋值方法,在一段时间后 ,size方法也可能返回较小的值,<br style="line-height:25px"> 对于isEmpty方法,可能返回false,然后返回true,对于给定的键,containsKey方法可能返回true然后返回false,对于给定的键,<br style="line-height:25px"> get方法可能返回一个值,但接着返回null,对于以前出现在映射中的键,put方法返回null,而remove方法返回false,<br style="line-height:25px"> 对于键集、值集、项集进行的检查,生成的元素数量越来越少。<br style="line-height:25px"><span style="color:#000080; line-height:25px">WeakHashMap中的每个键对象间接地存储为一个弱引用的指示对象。因此,不管是在映射内还是在映射之外,<br style="line-height:25px"> 只有在垃圾回收器清除某个键的弱引用之后,该键才会自动移除。</span><br style="line-height:25px"> 实现注意事项:<span style="color:#000080; line-height:25px">WeakHashMap中的值对象由普通的强引用保持。因此应该小心谨慎,确保值对象不会直接或间接地强引用其自身的键,<br style="line-height:25px"> 因为这会阻止键的丢弃</span>。注意,<span style="color:#000080; line-height:25px">值对象可以通过WeakHashMap本身间接引用其对应的键</span>;<br style="line-height:25px"> 这就是说,某个值对象可能强引用某个其他的键对象,而与该键对象相关联的值对象转而强引用第一个值对象的键。<br style="line-height:25px"> 处理此问题的一种方法是,在插入前将值自身包装在WeakReferences中,如:m.put(key,newWeakReference(value)),<br style="line-height:25px"> 然后,分别用get进行解包。<br style="line-height:25px"> 该类所有“collection视图方法”返回的迭代器均是快速失败的:在迭代器创建之后,<br style="line-height:25px"> 如果从结构上对映射进行修改,除非通过迭代器自身的remove或add方法,其他任何时间任何方式的修改,<br style="line-height:25px"> 迭代器都将抛出ConcurrentModificationException。因此,面对并发的修改,迭代器很快就完全失败,<br style="line-height:25px"> 而不是冒着在将来不确定的时间任意发生不确定行为的风险。<br style="line-height:25px"> 注意,迭代器的快速失败行为不能得到保证,一般来说,存在不同步的并发修改时,不可能作出任何坚决的保证。<br style="line-height:25px"> 快速失败迭代器尽最大努力抛出ConcurrentModificationException。因此,编写依赖于此异常程序的方式是错误的,<br style="line-height:25px"> 正确做法是:迭代器的快速失败行为应该仅用于检测bug。<br style="line-height:25px"><span style="line-height:25px"><wbr style="line-height:25px">注意1:</wbr></span><wbr style="line-height:25px">null值和null键都被支持。<br style="line-height:25px"><span style="line-height:25px"><wbr style="line-height:25px">注意2</wbr></span><wbr style="line-height:25px">:不是线程安全的。<br style="line-height:25px"><span style="line-height:25px"><wbr style="line-height:25px">注意3</wbr></span><wbr style="line-height:25px">:迭代器的快速失败行为不能得到保证。<br style="line-height:25px"><span style="line-height:25px"><wbr style="line-height:25px">注意4</wbr></span><wbr style="line-height:25px">:WeakHashMap是无序的。<br style="line-height:25px"><span style="line-height:25px"><wbr style="line-height:25px">注意5</wbr></span><wbr style="line-height:25px">:确保值对象不会直接或间接地强引用其自身的键,<br style="line-height:25px"><span style="color:#000080; line-height:25px">因为这会阻止键的丢弃。但是,值对象可以通过</span><span style="color:#ff6600; line-height:25px">WeakHashMap</span><span style="color:#000080; line-height:25px">本身间接引用其对应的键;</span><br style="line-height:25px"><span style="color:#000080; line-height:25px">这就是说,某个值对象可能强引用某个其他的键对象,而与该键对象相关联的值对象转而强引用第一个值对象的键,这时就形成了环路。</span><br style="line-height:25px"><span style="color:#000080; line-height:25px">处理此问题的一种方法是,</span><span style="color:#0000ff; line-height:25px">在插入前将值自身包装在</span><wbr style="line-height:25px"><span style="color:#ff6600; line-height:25px">WeakReferences</span><wbr style="line-height:25px"><span style="color:#0000ff; line-height:25px">中,如:m.put(key,newWeakReference(value)),<br style="line-height:25px"> 然后,分别用get进行解包</span><span style="color:#003366; line-height:25px">。</span>如实例1.<br style="line-height:25px"><span style="line-height:25px"><wbr style="line-height:25px">实例1</wbr></span><wbr style="line-height:25px">:<br style="line-height:25px"><span style="color:#3366ff; line-height:25px">importjava.lang.ref.WeakReference;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">importjava.util.LinkedHashMap;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">importjava.util.Map;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">importjava.util.Random;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">importjava.util.WeakHashMap;</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">importjava.util.concurrent.ConcurrentLinkedQueue;</span><br style="line-height:25px"><span style="color:#993300; line-height:25px">publicclass</span><span style="color:#3366ff; line-height:25px"></span><span style="color:#ff6600; line-height:25px">Test</span><span style="color:#3366ff; line-height:25px">{</span><br style="line-height:25px"><span style="color:#808080; line-height:25px">/**<br style="line-height:25px"> *@paramargs<br style="line-height:25px"> */</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px"></span><span style="color:#993300; line-height:25px">publicstaticvoid</span><span style="color:#3366ff; line-height:25px"></span><span style="color:#ff6600; line-height:25px">main</span><span style="color:#3366ff; line-height:25px">(String[]args){</span><br style="line-height:25px"><span style="color:#0000ff; line-height:25px">WeakHashMap&lt;Integer,</span><span style="color:#ff9900; line-height:25px">WeakReference</span><span style="color:#0000ff; line-height:25px">&lt;People&gt;&gt;map=newWeakHashMap&lt;Integer,</span><span style="color:#ff9900; line-height:25px">WeakReference</span><span style="color:#0000ff; line-height:25px">&lt;People&gt;&gt;();<br style="line-height:25px"> Peoplep1=newPeople("robin",1,28);<br style="line-height:25px"> Peoplep2=newPeople("robin",2,29);<br style="line-height:25px"> Peoplep3=newPeople("harry",3,30);<br style="line-height:25px"> map.put(newInteger(100),newWeakReference&lt;People&gt;(p1));<br style="line-height:25px"> map.put(newInteger(101),newWeakReference&lt;People&gt;(p2));<br style="line-height:25px"> map.put(newInteger(1),newWeakReference&lt;People&gt;(p3));<br style="line-height:25px"><br style="line-height:25px"> for(WeakReference&lt;People&gt;rp:map.values())<br style="line-height:25px"> {<br style="line-height:25px"> Peoplep=rp.get();<br style="line-height:25px"> System.out.println("people:"+p);<br style="line-height:25px"> }</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#3366ff; line-height:25px">}</span><br style="line-height:25px"><span style="color:#993300; line-height:25px">class</span><span style="color:#3366ff; line-height:25px">People{<br style="line-height:25px"> Stringname;<br style="line-height:25px"> intid;<br style="line-height:25px"> intage;<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">public</span><span style="color:#3366ff; line-height:25px">People(Stringname,intid)<br style="line-height:25px"> {<br style="line-height:25px"> this(name,id,0);<br style="line-height:25px"> }<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">public</span><span style="color:#3366ff; line-height:25px">People(Stringname,intid,intage)<br style="line-height:25px"> {<br style="line-height:25px"> this.name=name;<br style="line-height:25px"> this.id=id;<br style="line-height:25px"> this.age=age;<br style="line-height:25px"> }<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">publicString</span><span style="color:#3366ff; line-height:25px">toString()<br style="line-height:25px"> {<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">return</span><span style="color:#3366ff; line-height:25px">id+name+age;<br style="line-height:25px"> }<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">publicboolean</span><span style="color:#3366ff; line-height:25px">equals(Objecto)<br style="line-height:25px"> {<br style="line-height:25px"> if(o==null)<br style="line-height:25px"> returnfalse;<br style="line-height:25px"> if(!(oinstanceofPeople))<br style="line-height:25px"> returnfalse;<br style="line-height:25px"> Peoplep=(People)o;<br style="line-height:25px"> booleanres=name.equals(p.name);<br style="line-height:25px"> if(res)<br style="line-height:25px"> System.out.println("name"+name+"isdouble");<br style="line-height:25px"> else<br style="line-height:25px"> System.out.println(name+"vS"+p.name);<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">return</span><span style="color:#3366ff; line-height:25px">res;<br style="line-height:25px"> }<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">publicint</span><span style="color:#3366ff; line-height:25px">hashCode()<br style="line-height:25px"> {<br style="line-height:25px"></span><span style="color:#993300; line-height:25px">return</span><span style="color:#3366ff; line-height:25px">name.hashCode();<br style="line-height:25px"> }<br style="line-height:25px"> }</span></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>

来这里 http://hubingforever.blog.163.com/blog/static/1710405792010794452203/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值