Java 中的四種引用使用場景分析
強引用
強引用的物件,永遠不會被垃圾回收,JVM 寧願丟擲 OutOfMemeory 錯誤也不會回收這種物件。
軟引用 soft
沒有強引用而只有軟引用的物件,只要記憶體空間足夠,垃圾回收器就不會回收(嚴謹的說法是,GC 根據記憶體使用情況酌情考慮什麼時候回收)。
MyObject aStrongRef = new MyObject();
SoftReference aSoftRef = new SoftReference(aStrongRef);
aStongRef = null; // 此時這個物件只有軟引用,沒有強引用
aStrongRef = aSoftRef. get(); // 返回 null,或者新的強引用
當軟引用在 OutOfMemory 之前被回收後,軟引用變數不再有存在的價值,但是他們本身還是強引用,太多的軟引用變數同樣會導致 OutOfMemory 問題。其實,我們可以在建立軟引用變數時,指定一個 ReferenceQueue,當軟引用變數不再有存在的價值時,會被插入到佇列中,我們可以利用此佇列回收這些軟引用變數。
Set<SoftReference<MyObject>> cache = new HashSet<>();
cache.add(new SoftReference(aStrongRef, queue));
//...
Reference<? extends MyObject> ref = queue.poll();
while (ref != null) {
if (cache.remove(ref)) {
removedSoftRefs++;
}
ref = queue.poll();
}
軟引用可以被用於快取物件,尤其那些重新例項化的開銷很大的物件。
弱引用 weak
用來描述非必需物件,當 JVM 進行垃圾回收時,無論記憶體是否充足,都會回收沒有強、軟引用而只有弱引用的物件。
WeakHashMap 的特色是使用了弱引用的鍵,它可以被當做標準 Map 使用,不一樣的地方是,當鍵被 GC 回收時,它會自動清理該鍵值對。
public class ExampleWeakHashMap {
public static Map<Integer,String> cache = new WeakHashMap<Integer, String>();
public static void main(String[] args) {
Integer i5 = new Integer(5);
cache.put(i5, "five");
i5 = null;
// the entry {5,"five"} will stay in the Map until the next garbage collector call
Integer i2 = 2;
// the entry {2,"two"} will stay in the Map until i2 is no more strongly referenced
cache.put(i2, "two");
// the OutOfMemoryError won't happen, because the Map will clear its entries.
for (int i = 6; i < 100_000_000; i++) {
cache.put(i,String.valueOf(i));
}
}
}
在規範化對映中,可以使用 WeakHashMap<String,Map<K,V>>
儲存多個事務的資訊,String 型別的鍵儲存事務的 ID,簡單的 Map 中儲存了事務生命週期中需要的資訊,在事務的生命週期中,String 物件的強引用一直存在,所以我們可以一直獲取它的資訊,當事務結束後,弱引用可以自動幫助我們清理 Map 資訊。
虛引用 phantom
在垃圾回收程序中,沒有強、軟引用的物件會被刪除,在被刪除前,會先呼叫物件的 finalize() 方法。當一個物件被 finalized 但是還沒有被刪除完,它就處於“虛可達”的狀態,這意味著只有一個 GC 根節點和該物件之間的一個虛引用。
ReferenceQueue<String> queue = new ReferenceQueue<String>();
PhantomReference<String> aPhantomRef = new PhantomReference<String>(new String("string"), queue);
和軟、弱引用不一樣,對一個物件的顯式的虛引用會阻止該物件被刪除。程式設計師需要顯式或者隱式地移除此虛引用,才能使得 finalized 物件被銷燬。顯式地銷燬虛引用要用到 ReferenceQueue,當虛可達的物件被 finalized 後,佇列中會入隊此物件。但是虛引用不能獲得此物件,get() 方法總是返回 null,程式設計師不能使虛可達的物件再次強、軟、弱可達。這也很好理解,因為在重寫的 finalize() 方法中通常要清理各種資源,使得物件不能繼續工作。
虛引用的使用場景,可能是,當你需要在物件被 finalized 之後做一些處理,但是又不能重寫 finalize() 方法時(處於效能和可靠性的考慮,Effective Java 3 rd 中不建議使用 finalizer 和 cleaner)。