天天看點

WeakHashMap和Java引用類型詳細解析

WeakHashMap是種弱引用的HashMap,這是說,WeakHashMap裡的key值如果沒有外部強引用,在垃圾回收之後,WeakHashMap的對應内容也會被移除掉。

在講解WeakHashMap之前,我們需要了解Java中引用的相關類:

ReferenceQueue,引用隊列,與某個引用類綁定,當引用死亡後,會進入這個隊列。

HardReference,強引用,任何以類似String str=new String()建立起來的引用,都是強引用。在str指向另一個對象或者null之前,該String對象都不會被GC(Garbage Collector垃圾回收器)回收;

WeakReference,弱引用,可以通過java.lang.ref.WeakReference來建立,當GC要求回收對象時,它不會阻止對象被回收,該對象會立刻被回收;

SoftReference,軟引用,可以通過java.lang.ref.SoftReference來建立,和弱引用一樣,當GC要求回收時,它不會阻止對象被回收,但不同的是該對回收過程會被延遲,必須要等到JVM heap記憶體不夠用,接近産生OutOfMemory錯誤時,才會回收;

PhantomReference,虛引用,可以通過java.lang.ref.PhantomPeference來建立,這種類型的引用很特别,大多數時間,無法通過它拿到其引用的對象,但是,當這個對象死亡的時候,該引用還是會進入ReferenceQueue隊列。

下面提供一個例子來分别說明它們的作用:

ReferenceQueue<Ref> queue = new ReferenceQueue<Ref>();

// 建立一個弱引用

WeakReference<Ref> weak = new WeakReference<Ref>(new Ref("Weak"),queue);

// 建立一個虛引用

PhantomReference<Ref> phantom = new PhantomReference<Ref>(new Ref(

"Phantom"), queue);

// 建立一個軟引用

SoftReference<Ref> soft = new SoftReference<Ref>(new Ref("Soft"),queue);

System.out.println("引用内容:");

System.out.println(weak.get());

System.out.println(phantom.get());

System.out.println(soft.get());

System.out.println("被回收的引用:");

for (Reference r = null; (r = queue.poll()) != null;) {

System.out.println(r);

}

Ref這個類是個自定義的測試類,源碼如下所示:

class Ref {

Object v;

Ref(Object v) {

this.v = v;

public String toString() {

return this.v.toString();

在這個例子裡,分别建立了弱引用、虛引用和軟引用,get()方法用于擷取它們引用的Ref對象,可以注意到,Ref對象在外部并沒有任何引用,是以,在某個時間點,GC應當會回收對象。來看看代碼執行的結果:

引用内容:

Weak

null

Soft

被回收的引用:

可以看到,弱引用和軟引用的對象還是可達的,但是虛引用是不可達的。被回收的引用沒有内容,說明GC還沒有回收它們。

這證明了虛引用的性質:

虛引用非常弱,以至于它自己也找不到自己的引用内容。

對之前的代碼進行改造,在輸出内容前加入代碼:

// 強制垃圾回收

System.gc();

再執行一次,得到結果:

java.lang.ref.WeakReference@3b764bce

java.lang.ref.PhantomReference@759ebb3d

現在可達的引用隻剩下Soft了,引用隊列裡多出了兩條引用,說明WeakReference和PhantomReference的對象被回收。

再修改一次代碼,讓WeakPeference和PhantomReference去引用一個強引用對象:

Ref wr = new Ref("Hard");

WeakReference<Ref> weak = new WeakReference<Ref>(wr, queue);

PhantomReference<Ref> phantom = new PhantomReference<Ref>(wr, queue);

輸出結果如下所示:

Hard

這證明了弱引用的性質:

弱引用的對象,如果沒有被強引用,在垃圾回收後,引用對象會不可達。

WeakHashMap利用了ReferenceQueue和WeakReference來實作它的核心功能:當key值沒有強引用的時候,從WeakHashMap裡移除。

先來看看WeakHashMap的鍵值對實體類WeakHashMap.Entry的實作:

 private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {

        Entry(Object key, V value,

              ReferenceQueue<Object> queue,

              int hash, Entry<K,V> next) {

            super(key, queue);

            this.value = value;

            this.hash  = hash;

            this.next  = next;

        }

    ...

關鍵注意兩處:

1、Entry繼承自WeakReference;

2、Entry本身沒有儲存key值,而是把key作為WeakReference的引用對象交給了super構造。

這說明,Entry是個針對key值的弱引用。

WeakHashMap實作清除陳舊實體的方法是expungStaleEntries(),其源碼實作如下:

private void expungeStaleEntries() {

    //周遊引用隊列,找到每一個被GC收集的對象

        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;

                        //幫助GC執行

                        e.value = null;

                        size--;

                        break;

                    }

                    prev = p;

                    p = next;

                }

            }

    }

expungStaleEntries()方法會在resize、put、get、forEach方法裡被調用。