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將會保留
package test;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.WeakHashMap;
public class Test {
public static void main(String[] args) throws Exception {
String a = new String("a");
String b = new String("b");
Map weakmap = new WeakHashMap();
Map map = new HashMap();
map.put(a, "aaa");
map.put(b, "bbb");
weakmap.put(a, "aaa");
weakmap.put(b, "bbb");
map.remove(a);
a=null;
b=null;
System.gc();
Iterator i = map.entrySet().iterator();
while (i.hasNext()) {
Map.Entry en = (Map.Entry)i.next();
System.out.println("map:"+en.getKey()+":"+en.getValue());
}
Iterator j = weakmap.entrySet().iterator();
while (j.hasNext()) {
Map.Entry en = (Map.Entry)j.next();
System.out.println("weakmap:"+en.getKey()+":"+en.getValue());
}
}
}
先把問題說清楚:
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中)進行遍歷,并將entry的value置空,回收了內存.
所以效果是key在GC的時候被清除,value在key清除后訪問WeakHashMap被清除.
疑問:key的quene與map的quene是同一個quene,poll操作會減少一個reference,那問題是key如果先被清除,expungeStaleEntries遍歷quene時那個被回收的key對應的entry還能取出來么???
關于執行System.GC時,key中的byte數據如何被回收了,請見WeakReference referenceQuene
WeakHashMap
public class WeakHashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>
以弱鍵實現的基于哈希表的 Map。在 WeakHashMap 中,當某個鍵不再正常使用時,將自動移除其條目。
更精確地說,對于一個給定的鍵,其映射的存在并不阻止垃圾回收器對該鍵的丟棄,這就使該鍵成為可終止的,被終止,然后被回收。
丟棄某個鍵時,其條目從映射中有效地移除,因此,該類的行為與其他的 Map 實現有所不同。
null 值和 null 鍵都被支持。該類具有與 HashMap 類相似的性能特征,并具有相同的效能參數初始容量 和加載因子。
像大多數集合類一樣,該類是不同步的??梢允褂?Collections.synchronizedMap 方法來構造同步的 WeakHashMap。
該類主要與這樣的鍵對象一起使用,其 equals 方法使用 == 運算符來測試對象標識。
一旦這種鍵被丟棄,就永遠無法再創建了,所以,過段時間后在 WeakHashMap 中查找此鍵是不可能的,不必對其項已移除而感到驚訝。
該類十分適合與 equals 方法不是基于對象標識的鍵對象一起使用,比如,String 實例。
然而,對于這種可重新創建的鍵對象,鍵若丟棄,就自動移除 WeakHashMap 條目,這種表現令人疑惑。
WeakHashMap 類的行為部分取決于垃圾回收器的動作,所以,幾個常見的(雖然不是必需的)Map 常量不支持此類。
因為垃圾回收器在任何時候都可能丟棄鍵,WeakHashMap 就像是一個被悄悄移除條目的未知線程。
特別地,即使對 WeakHashMap 實例進行同步,并且沒有調用任何賦值方法,在一段時間后 ,size 方法也可能返回較小的值,
對于 isEmpty 方法,可能返回 false,然后返回 true,對于給定的鍵,containsKey 方法可能返回 true 然后返回 false,對于給定的鍵,
get 方法可能返回一個值,但接著返回 null,對于以前出現在映射中的鍵,put 方法返回 null,而 remove 方法返回 false,
對于鍵集、值集、項集進行的檢查,生成的元素數量越來越少。
WeakHashMap 中的每個鍵對象間接地存儲為一個弱引用的指示對象。因此,不管是在映射內還是在映射之外,
只有在垃圾回收器清除某個鍵的弱引用之后,該鍵才會自動移除。
實現注意事項:WeakHashMap 中的值對象由普通的強引用保持。因此應該小心謹慎,確保值對象不會直接或間接地強引用其自身的鍵,
因為這會阻止鍵的丟棄。注意,值對象可以通過 WeakHashMap 本身間接引用其對應的鍵;
這就是說,某個值對象可能強引用某個其他的鍵對象,而與該鍵對象相關聯的值對象轉而強引用第一個值對象的鍵。
處理此問題的一種方法是,在插入前將值自身包裝在 WeakReferences 中,如:m.put(key, new WeakReference(value)),
然后,分別用 get 進行解包。
該類所有“collection 視圖方法”返回的迭代器均是快速失敗的:在迭代器創建之后,
如果從結構上對映射進行修改,除非通過迭代器自身的 remove 或 add 方法,其他任何時間任何方式的修改,
迭代器都將拋出 ConcurrentModificationException。因此,面對并發的修改,迭代器很快就完全失敗,
而不是冒著在將來不確定的時間任意發生不確定行為的風險。
注意,迭代器的快速失敗行為不能得到保證,一般來說,存在不同步的并發修改時,不可能作出任何堅決的保證。
快速失敗迭代器盡最大努力拋出 ConcurrentModificationException。因此,編寫依賴于此異常程序的方式是錯誤的,
正確做法是:迭代器的快速失敗行為應該僅用于檢測 bug。
注意1:null 值和 null 鍵都被支持。
注意2:不是線程安全的。
注意3:迭代器的快速失敗行為不能得到保證。
注意4:WeakHashMap是無序的。
注意5:確保值對象不會直接或間接地強引用其自身的鍵,
因為這會阻止鍵的丟棄。但是,值對象可以通過 WeakHashMap 本身間接引用其對應的鍵;
這就是說,某個值對象可能強引用某個其他的鍵對象,而與該鍵對象相關聯的值對象轉而強引用第一個值對象的鍵,這時就形成了環路。
處理此問題的一種方法是,在插入前將值自身包裝在WeakReferences中,如:m.put(key, new WeakReference(value)),
然后,分別用 get 進行解包。如實例1.
實例1:
import java.lang.ref.WeakReference;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Random;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
public class Test {
/**
* @param args
*/
public static void main(String[] args) {
WeakHashMap<Integer,WeakReference<People>> map=new WeakHashMap<Integer,WeakReference<People>>();
People p1=new People("robin",1,28);
People p2=new People("robin",2,29);
People p3=new People("harry",3,30);
map.put(new Integer(100), new WeakReference<People>(p1));
map.put(new Integer(101), new WeakReference<People>(p2));
map.put(new Integer(1), new WeakReference<People>(p3));
for(WeakReference<People> rp:map.values())
{
People p= rp.get();
System.out.println("people:"+p);
}
}
}
class People{
String name;
int id;
int age;
public People(String name,int id)
{
this(name,id,0);
}
public People(String name,int id,int age)
{
this.name=name;
this.id=id;
this.age=age;
}
public String toString()
{
return id+name+age;
}
public boolean equals(Object o)
{
if(o==null)
return false;
if(!(o instanceof People))
return false;
People p=(People)o;
boolean res=name.equals(p.name);
if(res)
System.out.println("name "+name+" is double");
else
System.out.println(name+" vS "+p.name);
return res;
}
public int hashCode()
{
return name.hashCode();
}
}