1. 程式人生 > >Java原始碼分析——java.util工具包解析(四)——四大引用型別以及WeakHashMap類解析

Java原始碼分析——java.util工具包解析(四)——四大引用型別以及WeakHashMap類解析

    WeakHashMap是Map的一種很獨特的實現,從它的名字可以看出,它是存貯弱引用的對映的,先來複習一下Java中的四大引用型別:

  1. 強引用:我們使用的大部分引用實際上都是強引用,這是使用最普遍的引用。強引用的物件垃圾回收器絕不會回收它。當記憶體空間不足,jvm寧願丟擲OutOfMemoryError錯誤,使程式異常終止,也不會靠隨意回收具有強引用的物件來解決記憶體不足問題。
  2. 軟引用:軟引用是當jvm中記憶體不夠的情況下會回收其物件,在記憶體充足的情況下與強引用別無二樣。
  3. 弱引用:弱引用是隻要GC掃描到了弱引用,那麼它指向的物件就會不管記憶體是否充足都會進行回收。弱引用可以和一個引用佇列(ReferenceQueue)聯合使用,如果弱引用所引用的物件被垃圾回收,jvm就會把這個弱引用加入到與之關聯的引用佇列中。程式可以通過判斷引用佇列中是否已經加入了弱引用,來了解被引用的物件是否將要被垃圾回收。
  4. 虛引用:虛引用與沒有引用沒有什麼區別,相當於沒有引用指向改物件。虛 引用主要用來跟蹤物件被垃圾回收的活動。虛引用與軟引用和弱引用的一個區別在於:虛引用必須和引用佇列(ReferenceQueue)聯合使用。

    通過幾個例子來加深對四大引用型別的理解,先看第一個例子:

		String reference="蕾姆";//reference就是一個強引用
        SoftReference<String> stringSoftReference=new SoftReference<>(reference)
; reference=null; System.gc(); System.out.println(stringSoftReference.get());//輸出:蕾姆

    上述"蕾姆"有兩個引用指向它,一個是強引用reference,另外一個是軟引用stringSoftReference。從程式碼中可以看出,即便呼叫了GC,將強引用置為null,也依舊沒有回收其指向的物件。再看下一個,為了瞭解什麼時候物件回收了,重寫finalize方法:

class Rem{
    long[] l=new long
[10000]; @Override protected void finalize(){ System.out.println("小蕾姆被回收了"); } public String toString(){ return "你好,蕾姆"; } } public class Test { public static void main(String args[]) { WeakReference<Rem> weakReference=new WeakReference<>(new Rem()); SoftReference softReference=new SoftReference(new Rem()); System.gc(); System.out.println(softReference.get()); } } //輸出:小蕾姆被回收了 //你好,蕾姆

    為了使得GC能找到需要回收的物件,在Rem類裡定義了一個很大的陣列,方便GC進行回收,從程式碼可以看出,弱引用指向的物件是被回收了的,而軟引用則沒有被回收掉。理解了上述程式碼後,再來看看WeakHashMap類,該類繼承自AbstractMap抽象類,與HasnMap類相比,它多一個引用佇列:

public class WeakHashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V> {
    private static final int DEFAULT_INITIAL_CAPACITY = 16;
    private static final int MAXIMUM_CAPACITY = 1 << 30;
    private static final float DEFAULT_LOAD_FACTOR = 0.75f;
    Entry<K,V>[] table;
    private int size;
    private int threshold;
    private final float loadFactor;
    private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
}

    經過上述的引用型別的討論,很容易的得出結論,queue 裡存貯著是需要被GC回收掉的引用。同時需要注意的是HashMap類的鍵是null的話,從null得出的雜湊值是0,所以會存貯在第一個桶中,而WeakHashMap類則不一樣,它給鍵為null值定義了一個Object物件:

 	private static final Object NULL_KEY = new Object();
    private static Object maskNull(Object key) {
        return (key == null) ? NULL_KEY : key;
    }
    static Object unmaskNull(Object key) {
        return (key == NULL_KEY) ? null : key;
    }

    因此,鍵為null時與其他的鍵就沒什麼區別了。其中它的節點定義是繼承了WeakReference,也就是說它裡面儲存的所有節點都是弱引用:

private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
        V value;
        final int hash;
        Entry<K,V> next;
}

    在WeakHashMap的各項操作中,比如get()、put()、size()都間接或者直接呼叫了expungeStaleEntries()方法,以清理弱引用指向的key物件:

private void expungeStaleEntries() {
		//從引用佇列中迴圈取出需要被清理的引用
        for (Object x; (x = queue.poll()) != null; ) {
            synchronized (queue) {
                @SuppressWarnings("unchecked")
                    Entry<K,V> e = (Entry<K,V>) x;
                 //從桶中取出節點
                int i = indexFor(e.hash, 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;
                        //將被清理的鍵對應的值置為null
                        e.value = null; 
                        //節點數減一
                        size--;
                        break;
                    }
                    prev = p;
                    p = next;
                }
            }
        }
    }

    那麼,在什麼場景下可以用到WeakHashMap類呢?可以再存貯很大的值的場景下使用到WeakHashMap類,比如存貯上萬個節點時,利用HashMap存貯:

class Rem{
    int i;
    long[] l=new long[10000];
    public Rem(int i){
        this.i=i;
    }
    @Override
    protected void finalize(){
        System.out.println("小蕾姆被回收了");
    }
    public String toString(){
        return "你好,蕾姆";
    }
}
public class Test {
    public static void main(String args[]) {
        HashMap<Integer,Rem> map=new HashMap<>();
//        Map<Integer,Rem> map=new WeakHashMap<>();
        for (int i=0;i<10000;i++){
            map.put(i,new Rem(i));
        }
    }
}

    會報記憶體溢位的異常,而用WeakHashMap類來存貯則不會,但會一直顯示物件被回收掉。從中我們可以得到WeakHashMap的應用場景,這兩段程式碼比較可以看到WeakHashMap的功效,如果在系統中需要一張很大的Map表,Map中的表項作為快取只用,這也意味著即使沒能從該Map中取得相應的資料,系統也可以通過候選方案獲取這些資料。雖然這樣會消耗更多的時間,但是不影響系統的正常執行。在這種場景下,使用WeakHashMap是最合適的。因為WeakHashMap會在系統記憶體範圍內,儲存所有表項,而一旦記憶體不夠,在GC時,沒有被引用的表項又會很快被清除掉,從而避免系統記憶體溢位。