對象可觸及時的生命周期
在 JVM 1.2 之前,堆中的對象分為三種狀態,分別是:
1.可觸及的 -- 從根節點開始可追蹤到
2.可復活的 -- 從根節點開始追蹤不到,但有可能被終結方法觸及並復活。不僅僅是那些聲明了 finalize() 方法的對象,而是所有的對象都要經過可復活狀態
3.不可觸及的 -- 以上兩種可能性都不存在,可以真正回收它們所占據的內存了
版本 1.2 中,可觸及按強弱進一步細分為:
1.強可觸及 -- 即原來的可觸及,從根節點開始的任何直接引用,如一個局部變量或任何從強可觸及對象的實例引用的對象
2.軟可觸及 -- 表現為 SoftReference 所引用的對象
3.弱可觸及 -- 表現為 WeakReference 所引用的對象
4.影子可觸及 -- 表現為 PhantomReference 所引用的對象
SoftReference、WeakReference、PhantomReference 都是 java.lang.ref.Reference 類的子類。強引用與這三種弱引用之間最基本的差別是,強引用禁止引用目標被垃圾收集,而那三種引用不禁止。
要創建某一對象的軟引用、弱引用或是影子引用,只需簡單的包裝一下。例如,創建一個 cow 對象的軟用就寫成:
SoftReference softCow = new SoftReference(cow); //對於 WeakReference 和 PhantomReference 都是一樣的
這裡 softCow 是一個強引用,從 softCow 到 cow 是一個軟引用,也就預示著垃圾收集器從根節點開始只能通過一個軟引用才能觸及到這個 cow 對象。要切斷到 cow 的軟引用,使之不再軟可觸及,可調用 softCow.clear(),要獲取 cow 對象用 softCow.get()。
可觸及性狀態的變化
引入三個這樣的引用對於虛擬機是有用處的,垃圾收集器對強引用對象是不能肆意妄為,但是它可隨意更改百強可觸及對象的可觸性狀態。在軟引用、弱引用或者影子引用指向對象的可觸及狀態被垃圾收集器改變時,你可以獲得這變化發生的通知,方法是要把引用對象和引用隊列關聯起來。
引用隊列是 java.lang.ref.ReferenceQueue 類的實例,垃圾收集器在改變可觸及性狀態時會把所涉及的引用對象編入到隊列中。你只要設置並觀察引用隊列,便可異步得到通知了。
下面用代碼來演示一下 Reference、ReferenceQueue 與 Object 之間的關系,以及如何監聽到可觸及狀態的變化。
package com.unmi.ref;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
/**
* 測試 Reference
* @author Unmi
*/
public class TestReference {
public static void main(String[] args) throws InterruptedException {
final ReferenceQueue<Cow> queue = new ReferenceQueue<Cow>();//引用隊列
//引用和引用隊列進行關聯
SoftReference<Cow> ref1 = new SoftReference<Cow>(new Cow(1),queue);
final SoftReference<Cow> ref2 = new SoftReference<Cow>(new Cow(2),queue);
System.out.println(queue.poll());//poll()方法取不到對象即刻返回null
ref1.enqueue(); //把ref1所引用的對象cow1編入到引用隊列中
System.out.println(queue.poll().get()); //用poll()取隊列上的對象
new Thread(){ //啟動一個線程來監測引用隊列中的對象
public void run(){
try {
//用remove()以阻塞方式獲取並移走對象,不見對象不死心
System.out.println(queue.remove().get());
System.out.println("線程獲取到了引用隊列中的對象");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
System.out.println("等待3秒鐘後把cow2編入隊列...");
Thread.sleep(3000);
ref2.enqueue(); //把ref2所引用的對象cow2編入到引用隊列中
}
}
class Cow{
private int num;
public Cow(int num){
this.num = num;
}
public String toString(){
return "This is Cow " + this.num;
}
}
上面程序執行後的輸出為:
null
This is Cow 1
等待 3 秒鐘後把 cow2 編入隊列...
This is Cow 2
線程獲取到了引用隊列中的對象
本想說明一下 Reference 的 clear() 方法的效果,但通過程序不容易展現出來。
當垃圾收集器決定收集軟可觸及的 Cow 對象時,它會清除 SoftReference (調用它的 clear() 方法),可能立即或在稍後把它所涉及的 Cow 對象放到引用隊列中。何時加入隊列是沒法確定的,所以代碼不好演示。
垃圾收集器要調用 Reference 的 enqueue() 方法就會把清除的對象加到引用隊列中,當然你也可以手工調用該方法。在引用隊列上可用 poll() 和 remove() 方法來獲取對象,它們的區別是一個非阻塞,無對象立即返回 null,而 remove() 是阻塞的守候,不等到不罷休。
再來看看垃圾收集器對那三種引用對象的處理方式。
軟引用:GC 可能回收它所引用對象占據的內存。如果發生了,便解除(clear()) 引用,並加入引用隊列
弱引用:GC 必須歸還它所引用對象占據的內存。如果發生了,便解除(clear()) 引用,並加入引用隊列
影子引用:GC 立即把它所引用對象加入隊列,但從不解除(clear())影子引用,所有的影子引用都必須由程序明確的清除。
緩存、規范映射和臨終清理
軟、弱、影子引用可分別為程序提供不同的服務。軟引用可用來創建內存中的緩存;弱引用可以創建規范映射,如哈稀表,它的 Key 和 Value 可在無其他程序引用時從映射中清除;影子引用使你可實現除終結方法以外的更為復雜的臨終清理策略。
軟、弱引用未清除時,get() 能獲取到相應對象,否則返回 null,而影子引用的 get() 方法總是返回 null。如果一個對象到達了影子可觸及狀態,它便不能再被復活了,等著被回收吧。
虛擬機在內存緊張時會考慮釋放軟、弱引用所關聯對象占據的內存。所不同的是垃圾收集器這時候可自行決定是否清除軟連接,但必須立即清除弱連接。弱引用的一個實現是 java.util.WeakHashMap 類。影子可觸及對角表示對象即將被回收,影子引用對象創建時必須關聯一個引用隊列的。