Java弱引用與WeakHashMap

yangxi_001發表於2014-03-11
在《Effective Java 2nd Edition》中,第6條“消除過期的物件引用”提到,雖然Java有 垃圾回收機制,但是隻要是自己管理的記憶體,就應該警惕記憶體洩露的問題,例如的物件池、快取中的過期物件都有可能引發記憶體洩露的問題。書中還提到可以用 WeakHashMap來作為快取的容器可以有效解決這一問題。之前也確實遇到過類似問題,但是沒有接觸過“弱引用”相關的問題,於是查閱了一些資料。    《Java 理論與實踐: 用弱引用堵住記憶體洩漏》一文也指出了使用全域性的Map作為快取容器時發生的記憶體洩露問題,介紹瞭如何使用hprof工具來找出記憶體洩露,並分析瞭如何使用 弱引用來防止記憶體洩露,還分析了WeakHashMap的關鍵程式碼,非常有參考價值。但是這篇文章遺漏了幾個很重要的需要注意的地方,也缺少一段實驗代 碼,本文將會做出適當補充。
    1、四種引用
    從JDK1.2版本開始,把物件的引用分為四種級別,從而使程式能更加靈活的控制物件的生命週期。這四種級別由高到低依次為:強引用、軟引用、弱引用和虛引用。
    強引用:平時我們程式設計的時候例如:Object object=new Object();那object就是一個強引用了。如果一個物件具有強引用,那就類似於必不可少的生活用品,垃圾回收器絕不會回收它。當記憶體空 間不足,Java虛擬機器寧願丟擲OutOfMemoryError錯誤,使程式異常終止,也不會靠隨意回收具有強引用的物件來解決記憶體不足問題。
    軟引用(SoftReference):如果一個物件只具有軟引用,那就類似於可有可物的生活用品。如果記憶體空間足夠,垃圾回收器就不會回收它,如果記憶體 空間不足了,就會回收這些物件的記憶體。只要垃圾回收器沒有回收它,該物件就可以被程式使用。軟引用可用來實現記憶體敏感的快取記憶體。 軟引用可以和一個引用佇列(ReferenceQueue)聯合使用,如果軟引用所引用的物件被垃圾回收,Java虛擬機器就會把這個軟引用加入到與之關聯 的引用佇列中。
    弱引用(WeakReference):如果一個物件只具有弱引用,那就類似於可有可物的生活用品。弱引用與軟引用的區別在於:只具有弱引用的物件擁有更 短暫的生命週期。在垃圾回收器執行緒掃描它 所管轄的記憶體區域的過程中,一旦發現了只具有弱引用的物件,不管當前記憶體空間足夠與否,都會回收它的記憶體。不過,由於垃圾回收器是一個優先順序很低的執行緒, 因此不一定會很快發現那些只具有弱引用的物件。  弱引用可以和一個引用佇列(ReferenceQueue)聯合使用,如果弱引用所引用的物件被垃圾回收,Java虛擬機器就會把這個弱引用加入到與之關聯 的引用佇列中。
    虛引用(PhantomReference):“虛引用”顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用並不會決定物件的生命週期。如果一個物件 僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收。 虛引用主要用來跟蹤物件被垃圾回收的活動。虛引用與軟引用和弱引用的一個區別在於:虛引用必須和引用佇列 (ReferenceQueue)聯合使用。當垃圾回收器準備回收一個物件時,如果發現它還有虛引用,就會在回收物件的記憶體之前,把這個虛引用加入到與之 關聯的引用佇列中。程式可以通過判斷引用佇列中是否已經加入了虛引用,來了解被引用的物件是否將要被垃圾回收。程式如果發現某個虛引用已經被加入到引用隊 列,那麼就可以在所引用的物件的記憶體被回收之前採取必要的行動。
    2、WeakHashMap原始碼分析
    WeakHashMap維護了一個ReferenceQueue,儲存了所有存在引用的Key物件。
    private final ReferenceQueue<K> queue = new ReferenceQueue<K>();
    WeakHashMap.Entry<K,V>中並沒有儲存Key,只是將Key與ReferenceQueue關聯上了。
    private static class Entry<K,V> extends WeakReference<K> implements Map.Entry<K,V> {
    private V value;
    private final int hash;
    private Entry<K,V> next;
    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;
    }
    ……
    }
    WeakHashMap中有一個私有的expungeStaleEntries()方法,會在大部分共有方法中被呼叫。這個方法會將ReferenceQueue中所有失效的引用從Map中去除。
    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;
    }
    }
    }
    3、幾個需要注意的地方
    WeakHashMap的Key是弱引用,Value不是。
    WeakHashMap不會自動釋放失效的弱引用,僅當包含了expungeStaleEntries()的共有方法被呼叫的時候才會釋放。
    4、一個簡單的例子
    public static void main(String args[]) {
    WeakHashMap<String, String> map = new WeakHashMap<String, String>();
    map.put(new String("1"), "1");
    map.put("2", "2");
    String s = new String("3");
    map.put(s, "3");
    while (map.size() > 0) {
    try {
    Thread.sleep(500);
    } catch (InterruptedException ignored) {
    }
    System.out.println("Map Size:"+map.size());
    System.out.println(map.get("1"));
    System.out.println(map.get("2"));
    System.out.println(map.get("3"));
    System.gc();
    }
    }
    執行結果是:
    Map Size:3
    1
    2
    3
    Map Size:2
    null
    2
    3
    Map Size:2
    null
    2
    3
    (一直迴圈)
    要注意String的特殊性,“2”是被放在常量池中的,所以沒有被回收。

相關文章