網上關於軟引用、弱引用的文章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
}
}