理解 Java 的 GC 與 幽靈引用

  Java 中一共有 4 種類型的引用 : StrongReference、 SoftReference、 WeakReference 以及 PhantomReference (傳說中的幽靈引用 呵呵), 
這 4 種類型的引用與 GC 有着密切的關係,  讓咱們逐一來看它們的定義和使用場景 :
        1. Strong Reference
        StrongReference 是 Java 的默認引用實現,  它會盡量長時間的存活於 JVM 內, 當沒有任何對象指向它時 GC 執行後將會被回收
@Test
public void strongReference() {
 Object referent = new Object();
 
 /**
 * 經過賦值建立 StrongReference 
 */
 Object strongReference = referent;
 
 assertSame(referent, strongReference);
 
 referent = null;
 System.gc();
 
 /**
 * StrongReference 在 GC 後不會被回收
 */
 assertNotNull(strongReference);
}

 2. WeakReference & WeakHashMap

WeakReference, 顧名思義,  是一個弱引用,  當所引用的對象在 JVM 內再也不有強引用時, GC 後 weak reference 將會被自動回收 html

@Test
public void weakReference() {
	Object referent = new Object();
	WeakReference<Object> weakRerference = new WeakReference<Object>(referent);

	assertSame(referent, weakRerference.get());
	
	referent = null;
	System.gc();
	
	/**
	 * 一旦沒有指向 referent 的強引用, weak reference 在 GC 後會被自動回收
	 */
	assertNull(weakRerference.get());
}
WeakHashMap 使用 WeakReference 做爲 key, 一旦沒有指向 key 的強引用, WeakHashMap 在 GC 後將自動刪除相關的 entry
@Test
public void weakHashMap() throws InterruptedException {
	Map<Object, Object> weakHashMap = new WeakHashMap<Object, Object>();
	Object key = new Object();
	Object value = new Object();
	weakHashMap.put(key, value);

	assertTrue(weakHashMap.containsValue(value));
	
	key = null;
	System.gc();
	
	/**
	 * 等待無效 entries 進入 ReferenceQueue 以便下一次調用 getTable 時被清理
	 */
	Thread.sleep(1000);
	
	/**
	 * 一旦沒有指向 key 的強引用, WeakHashMap 在 GC 後將自動刪除相關的 entry
	 */
	assertFalse(weakHashMap.containsValue(value));
}
   3. SoftReference

SoftReference 於 WeakReference 的特性基本一致, 最大的區別在於 SoftReference 會盡量長的保留引用直到 JVM 內存不足時纔會被回收(虛擬機保證), 這一特性使得 SoftReference 很是適合緩存應用
@Test
public void softReference() {
 Object referent = new Object();
 SoftReference<Object> softRerference = new SoftReference<Object>(referent);

 assertNotNull(softRerference.get());
 
 referent = null;
 System.gc();
 
 /**
 *  soft references 只有在 jvm OutOfMemory 以前纔會被回收, 因此它很是適合緩存應用
 */
 assertNotNull(softRerference.get());
}
 4. PhantomReference

        做爲本文主角, Phantom Reference(幽靈引用) 與 WeakReference 和 SoftReference 有很大的不一樣,  由於它的 get() 方法永遠返回 null, 這也正是它名字的由來
@Test
public void phantomReferenceAlwaysNull() {
	Object referent = new Object();
	PhantomReference<Object> phantomReference = new PhantomReference<Object>(referent, new ReferenceQueue<Object>());
	
	/**
	 * phantom reference 的 get 方法永遠返回 null 
	 */
	assertNull(phantomReference.get());
}
諸位可能要問, 一個永遠返回 null 的 reference 要來何用,  請注意構造 PhantomReference 時的第二個參數 ReferenceQueue(事實上 WeakReference & SoftReference 也能夠有這個參數),
PhantomReference 惟一的用處就是跟蹤 referent  什麼時候被 enqueue 到 ReferenceQueue 中.

     5. RererenceQueue

當一個 WeakReference 開始返回 null 時, 它所指向的對象已經準備被回收, 這時能夠作一些合適的清理工做.   將一個 ReferenceQueue 傳給一個 Reference 的構造函數, 當對象被回收時, 虛擬機會自動將這個對象插入到 ReferenceQueue 中, WeakHashMap 就是利用 ReferenceQueue 來清除 key 已經沒有強引用的 entries.
@Test
public void referenceQueue() throws InterruptedException {
	Object referent = new Object();		
	ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();
	WeakReference<Object> weakReference = new WeakReference<Object>(referent, referenceQueue);
	assertFalse(weakReference.isEnqueued());
	Reference<? extends Object> polled = referenceQueue.poll();
	assertNull(polled);
	referent = null;
	System.gc();

	assertTrue(weakReference.isEnqueued());
	Reference<? extends Object> removed = referenceQueue.remove();
	assertNotNull(removed);
}
6.  PhantomReference  vs WeakReference

PhantomReference  有兩個好處, 其一, 它可讓咱們準確地知道對象什麼時候被從內存中刪除, 這個特性能夠被用於一些特殊的需求中(例如 Distributed GC,  XWork 和 google-guice 中也使用 PhantomReference 作了一些清理性工做). 

其二, 它能夠避免 finalization 帶來的一些根本性問題, 上文提到 PhantomReference 的惟一做用就是跟蹤 referent 什麼時候被 enqueue 到 ReferenceQueue 中,  可是 WeakReference 也有對應的功能, 二者的區別到底在哪呢 ?
這就要說到 Object 的 finalize 方法, 此方法將在 gc 執行前被調用, 若是某個對象重載了 finalize 方法並故意在方法內建立自己的強引用,  這將致使這一輪的 GC 沒法回收這個對象並有可能
引發任意次 GC, 最後的結果就是明明 JVM 內有不少 Garbage 卻 OutOfMemory, 使用 PhantomReference 就能夠避免這個問題, 由於 PhantomReference 是在 finalize 方法執行後回收的,也就意味着此時已經不可能拿到原來的引用,  也就不會出現上述問題,  固然這是一個很極端的例子, 通常不會出現. 

taken from http://mindprod.com/jgloss/phantom.html java


11 緩存

相關文章
相關標籤/搜索