二級(jí)java輔導(dǎo):有關(guān)于JVM的垃圾收集(三)

字號(hào):

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