Java 中一共有4 種類型的引用: StrongReference、 SoftReference、 WeakReference 以及PhantomReference (傳說中的幽靈引用呵呵),
這4 種類型的引用與GC 有著密切的關係, 讓我們逐一來看它們的定義和使用場景:
1、 Strong Reference
StrongReference 是Java 的預設參考實作, 它會盡可能長時間的存活於JVM 內, 當沒有任何物件指向它時GC 執行後將會被回收
Java程式碼
複製代碼代碼如下:
@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 將會被自動回收
複製代碼代碼如下:
@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, 這也正是它名字的由來
Java程式碼
複製代碼代碼如下:
@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.
Java程式碼
複製代碼代碼如下:
@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方法執行後回收的,也就意味著此時已經不可能拿到原來的引用, 也就不會出現上述問題, 當然這是一個很極端的例子, 一般不會出現.
7、 對比
Soft vs Weak vs Phantom References | ||||
---|---|---|---|---|
Type | Purpose | Use | When GCed | Implementing Class |
Strong Reference | An ordinary reference. Keeps objects alive as long as they are referenced. | normal reference. | Any object not pointed to can be reclaimed. | default |
Soft Reference | Keeps objects alive provided there's enough memory. | to keep objects alive even after clients have removed their references (memory-sensitive caches), in case clients start asking for them again by key. | After a first gc pass, the JVM decides it still needs to reclaim more space. | java.lang.ref.SoftReference |
Weak Reference | Keeps objects alive only while they're in use (reachable) by clients. | Containers that automatically delete objects no longer in use. | After gc determines the object is only weakly reachable | java.lang.ref.WeakReference java.util.WeakHashMap |
Phantom Reference | Lets you clean up after finalization but before the space is reclaimed (replaces or augments the use offinalize()) | Special clean up processing | After finalization. | java.lang.ref.PhantomReference |
8. 小結
一般的應用程式不會涉及Reference 編程, 但是了解這些知識會對理解GC 的工作原理以及性能調優有一定幫助,在實現一些基礎性設施比如緩存時也可能會用到, 希望本文能有所幫助。