你真的懂軟引用、弱引用、虛引用嗎?

網上關於軟引用、弱引用的文章10篇有9篇是錯的。而錯誤的原因,就在於一個基本概念沒有弄清楚。

引用類型分爲如下幾種:

強引用:普通對象引用。

軟引用:如果內存不夠就自動清理。(前提是沒有強引用指向被引用對象)

弱引用:垃圾回收機制一運行,不管內存夠不夠,它都會被清理。(前提是沒有強引用指向被引用對象)

虛引用:跟蹤對象被GC的狀態。必須與ReferenceQueue配合使用。

要理解這幾種引用,最重要的概念是理解“清理(clear)”這個概念。注意,我在這裏絕對沒有使用“回收”這個詞

我們來看下面這兩句:

    Object obj = new Object();
    SoftReference sRef = new SoftReference(obj); // No generics, bite me.

執行後,在內存中大體如下:

棧上有兩個變量,obj和sRef,分別指向堆裏的兩個具體對象,注意,這兩個引用都是強引用(粗箭頭所示),也就是說一個軟引用對象和普通對象沒什麼不同。而堆中的軟引用對象對實際對象(referent)的引用,是軟引用(細箭頭所示)。

“清理”的語義,就是把這個細箭頭斷開(如圖中紅叉所示)。“清理”既不是指回收堆裏的軟引用對象,也不是回收被引用對象即referent,而是切斷這個軟引用,即切斷堆裏這兩個對象間的聯繫;使得sRef.get()返回null。

這是重點:“清理”的意思是切斷軟引用對象與被引用對象的聯繫,不是任何回收動作本身。

有兩種方法“清理”軟引用:第一,程序主動調用Reference的clear()函數;第二,當GC“感到”內存可能不足,且referent沒有強引用時(此時稱作softly reachable),自動切斷其聯繫;如果註冊了ReferenceQueue則在切斷後將軟引用對象入Queue。

WeakReference與SoftReference的唯一不同是它不管內存夠不夠,下一次GC運行時就執行自動清理(以及入Queue)。

那麼,指向referent的所有軟引用都被清理後,referent是不是就立刻被回收了?不一定。一方面,可能有強引用還在引用它(比如上面我們沒有讓obj=null);另一方面,就算再沒有別的引用了,如果它覆寫了finalize()函數,那麼就還要經過finalizable標記、finalization等步驟之後纔會被回收,也就是說要等到下一次GC。

至於那個軟引用對象,它就是一個普通對象,對它的回收跟其它對象沒什麼不同,比如令sRef=null,它就變成可回收的了。

Weak和Soft都可以與引用隊列配合使用,也可以不。隊列是一個工具,用來通知程序:referent不可達了。比如WeakHashMap就用了Queue,而ThreadLocal沒有用,他們本質上是一種東西,一種思想的產物。都是將實際Map.Entry裏的那個key變爲指向put(key, value)的那個key的弱引用(確切地說,Entry本身繼承自WeakReference,它指向原始key,value仍作爲Entry的屬性存在)。這樣,當你在Map外面不再有對這個key的強引用了,GC運行時就會執行清理,那個Entry裏的key就get到null,此時就要把這個Entry刪掉。不同之處在於WeakHashMap用Queue,在任何時候訪問這個Map都調用一次ReferenceQueue.poll(),來看是否有null的key,有的話就刪掉對應的Entry。而ThreadLocal不使用Queue,當你調用get、set、remove等操作時,會調用一個叫expungeStaleEntry的方法,它會遍歷ThreadLocalMap,刪掉那些key爲null的Entry。

虛引用則是一個很無厘頭的東西,它的get方法任何時候都返回null。這意味着它沒什麼用。唯一作用是當referent處於可回收狀態時自己就入Queue,使程序知道有這麼回事,以便做些掃尾工作,代替finalize函數;它比finalize函數強的地方在於它永遠得不到referent,因此無法將之“復活”。

finalize函數有個悖論問題,那就是,finalize本來是在GC時調用的,但在finalize裏卻可以復活這個對象,比如anotherObj.someProp=this;一下子把它起死回生了,於是本次GC反而不能回收它。

https://www.baeldung.com/java-phantom-reference

在JDK9之前,PhantomReference行爲與WeakReference和SoftReference都不同,它入Queue前不會自動清理,JDK9之後改成和其它Reference行爲一致了。

下面附送一個WeakHashMap驗證小程序。演示它的自刪。

import java.util.WeakHashMap;

public class WeakHashMapTest {

public static void main(String[] args) throws Exception {

        WeakHashMap m = new WeakHashMap();
        
        for(int i=0; i<200; i++) {
            Object key = new String("key" + i);
            Object val = new Integer(i);
            m.put(key, val);

        }
        
        System.out.println(m.get("key42")); // 42
        System.out.println(m.size()); // 200
        
        // force gc!
        System.gc(); 
        System.runFinalization();
        
        m.remove("key42"); // remove() causes enqueue and deleting of entries
        System.out.println(m.size()); // 0

    }
}


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章