天天看點

Hotspot 對象引用Reference和Finalizer 源碼解析

目錄

一、Reference

1、SoftReference / WeakReference / PhantomReference

2、定義

3、ReferenceHandler

4、Cleaner

二、ReferenceQueue

1、定義

2、  enqueue / reallyPoll

3、poll / remove 

4、forEach

三、Finalizer

1、定義

2、register

3、FinalizerThread

4、runFinalization / runAllFinalizers

5、InstanceKlass::register_finalizer

一、Reference

      Reference是所有表示對象引用的類的抽象基類,定義了公共的方法,Reference所引用的對象稱為referent。因為Reference的實作與JVM的垃圾回收機制是強相關的,是以不建議直接繼承Reference,避免改變Reference的關鍵實作,其類繼承關系如下圖:

Hotspot 對象引用Reference和Finalizer 源碼解析

下面逐一講解各類的使用和源碼實作細節。

1、SoftReference / WeakReference / PhantomReference

     這三個對象的表示引用是越來越弱的,SoftReference通常用來實作記憶體敏感的緩存,當記憶體不足的時候,為了擷取可用記憶體空間會回收SoftReference所引用的對象,SoftReference本身會被放到建立時傳入的ReferenceQueue中,JVM保證在抛出OutOfMemoryError異常前,清除所有的SoftReference。SoftReference增加了兩個屬性:

  • static long clock; //由垃圾回收器維護的表示時間的字段
  • long timestamp; //用來儲存目前的clock

SoftReference改寫了原來的構造方法和get方法的實作,增加了timestamp屬性的更新邏輯,如下:

public SoftReference(T referent) {
        super(referent);
        this.timestamp = clock;
}

public SoftReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
        this.timestamp = clock;
    }

public T get() {
        T o = super.get();
        if (o != null && this.timestamp != clock)
            this.timestamp = clock;
        return o;
    }
           

      WeakReference通常用來實作類似WeakMap的特殊Map,不能阻止key或者value被垃圾回收了,當垃圾回收器發現一個對象隻是被WeakReference所引用就會回收掉該對象,并将關聯的WeakReference加入到建立時傳入的ReferenceQueue中。WeakReference沒有新增屬性,隻是定義了自己的構造方法而已,如下:

public WeakReference(T referent) {
        super(referent);
    }

public WeakReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
           

     PhantomReference通常用來實作一種更流暢的類似Object.finalize的清理功能,與SoftReference和WeakReference不同的是,PhantomReference的get方法永遠傳回null,為了保證其所引用的對象一直處于可被回收的狀态,并且當垃圾回收器判斷某個對象隻是被PhantomReference所引用,然後将PhantomReference加入到建立時傳入的ReferenceQueue中,這個時候垃圾回收器不會立即回收掉PhantomReference所引用的對象,而是等到所有的PhantomReference對象都放到ReferenceQueue中或者PhantomReference對象本身變得不可達。WeakReference也沒有新增屬性,改寫了原有的get方法實作,永遠傳回null,如下:

public T get() {
        return null;
    }

public PhantomReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
           

這三個類的代碼調用都大同小異,具體可以參考《java8 WeakHashMap接口實作源碼解析》中WeakReference的應用。

2、定義

   Reference包含的屬性如下:

  • T referent; //所引用的對象
  • volatile ReferenceQueue<? super T> queue; //當所引用的對象referent被清理時用來儲存Reference的隊列,調用方可以通過ReferenceQueue的poll方法擷取隊列中的Reference執行個體,進而知道哪些referent對象被回收掉了
  • volatile Reference next; //ReferenceQueue使用,通過next屬性将所有加入到ReferenceQueue中的Reference執行個體構成一個連結清單
  • transient Reference<T> discovered; //JVM使用的,用于将所有的Pending狀态的Reference執行個體構成一個連結清單
  • static Lock lock = new Lock(); //用來修改Pending狀态的Reference執行個體連結清單的鎖
  • static Reference<Object> pending  = null; //Pending狀态的Reference執行個體連結清單的連結清單頭元素,垃圾回收器發現某個對象隻有Reference執行個體引用,就會把Reference對象加入到這個連結清單中,而ReferenceHandler Thread不斷從這個連結清單中移除元素,将其加入到Reference執行個體建立時傳入的ReferenceQueue隊列中

  Reference定義了四種内部狀态:

  • Active 新建立的Reference執行個體就是Active狀态,當垃圾回收器發現所引用的對象referent的可達狀态發生變更了,可能将Reference執行個體的狀态改成Pending或者Inactive,取決于Reference執行個體建立時是否傳入ReferenceQueue執行個體引用。如果是從Active改成Pending,則垃圾回收器還會将該Reference執行個體加入到pending屬性對應的Reference清單中
  • Pending  pending屬性對應的Reference清單中的Reference執行個體的狀态都是Pending,等待ReferenceHandler Thread将這些執行個體加入到queue隊列中
  • Enqueued queue屬性對應的ReferenceQueue隊列中的Reference執行個體的狀态都是Enqueued,當執行個體從ReferenceQueue隊列中移除就會變成Inactive。如果Reference執行個體在建立時沒有傳入ReferenceQueue,則永遠不會處于Enqueued狀态。
  • Inactive 變成Inactive以後,狀态就不會再變更,等待垃圾回收器回收掉該執行個體

 在不同的狀态下,queue和next屬性的指派會發生變更,如下:

  • Active  queue就是Reference執行個體建立時傳入的ReferenceQueue引用,如果沒有傳入或者傳入的是null,則為ReferenceQueue.NULL;此時next屬性為null。
  • Pending queue就是Reference執行個體建立時傳入的ReferenceQueue引用,next屬性是this
  • Enqueued  queue就是ReferenceQueue.ENQUEUED,next屬性就是隊列中的下一個元素,如果目前Reference執行個體就是最後一個,則是this
  • Inactive queue就是ReferenceQueue.NULL,next屬性就是this

 Reference定義的方法比較簡單,如下:

Hotspot 對象引用Reference和Finalizer 源碼解析

其中get方法傳回所引用的對象referent,clear方法用于将referent置為null,enqueue方法用于将目前Reference執行個體加入到建立時傳入的queue隊列中,isEnqueued方法判斷目前Reference執行個體是否已加入queue隊列中,tryHandlePending方法是ReferenceHandler Thread調用的用于處理Pending狀态的Reference執行個體的核心方法,是包級通路的。重點關注ReferenceHandler的實作。

3、ReferenceHandler

     ReferenceHandler繼承自Thread,表示一個不斷将Pending狀态的Reference執行個體放入該執行個體建立時傳入的ReferenceQueue執行個體中,所有處于Pending狀态的Reference執行個體通過discovered執行個體屬性構成連結清單,連結清單頭就是Reference類的靜态屬性pending,在周遊連結清單時,如果連結清單為空則通過lock.wait()的方式等待;如果周遊的Reference執行個體是Cleaner,則調用其clean方法,用于清理資源清理。其實作如下:

private static class ReferenceHandler extends Thread {

        private static void ensureClassInitialized(Class<?> clazz) {
            try {
                Class.forName(clazz.getName(), true, clazz.getClassLoader());
            } catch (ClassNotFoundException e) {
                throw (Error) new NoClassDefFoundError(e.getMessage()).initCause(e);
            }
        }

        static {
            //預先加載并初始化兩個類
            ensureClassInitialized(InterruptedException.class);
            ensureClassInitialized(Cleaner.class);
        }

        ReferenceHandler(ThreadGroup g, String name) {
            super(g, name);
        }

        public void run() {
            while (true) {
                //while true不斷執行
                tryHandlePending(true);
            }
        }
    }


static boolean tryHandlePending(boolean waitForNotify) {
        Reference<Object> r;
        Cleaner c;
        try {
            //注意lock和pending都是靜态屬性
            synchronized (lock) {
                if (pending != null) {
                    //如果存在待處理的Reference執行個體
                    r = pending;
                    //Cleaner是PhantomReference的子類
                    c = r instanceof Cleaner ? (Cleaner) r : null;
                    //通過discovered屬性将所有Pending的Reference執行個體構成一個連結清單
                    //擷取下一個處于Pending的Reference執行個體
                    pending = r.discovered;
                    //discovered屬性置為空
                    r.discovered = null;
                } else {
                    //waitForNotify預設為true,阻塞目前線程直到其他線程喚醒
                    if (waitForNotify) {
                        lock.wait();
                    }
                    // retry if waited
                    return waitForNotify;
                }
            }
        } catch (OutOfMemoryError x) {
            //yield方法會讓出目前線程的CPU處理時間,讓垃圾回收線程擷取更多的CPU時間,加速垃圾回收
            Thread.yield();
            return true;
        } catch (InterruptedException x) {
            // retry
            return true;
        }

        if (c != null) {
            //pending屬性是Cleaner,執行清理
            c.clean();
            return true;
        }

        //pending屬性不是Cleaner
        //Pending狀态下,r.queue就是最初r建立時傳入的ReferenceQueue引用
        ReferenceQueue<? super Object> q = r.queue;
        //将r加入到queue 隊列中
        if (q != ReferenceQueue.NULL) q.enqueue(r);
        return true;
    }
           

  ReferenceHandler的啟動是通過靜态static塊完成的,如下:

static {
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        //往上周遊找到最初的父線程組
        for (ThreadGroup tgn = tg;
             tgn != null;
             tg = tgn, tgn = tg.getParent());
        //建立ReferenceHandler線程,并啟動     
        Thread handler = new ReferenceHandler(tg, "Reference Handler");
        handler.setPriority(Thread.MAX_PRIORITY); //最高優先級
        handler.setDaemon(true); //背景線程
        handler.start();

        //允許通路SharedSecrets
        SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
            @Override
            public boolean tryHandlePendingReference() {
                return tryHandlePending(false);
            }
        });
    }
           

4、Cleaner

     Cleaner繼承自PhantomReference,其源碼可以參考OpenJDK jdk\src\share\classes\sun\misc\Cleaner.java。Cleaner表示一種更輕量更健壯的資源清理方式,相對于Object的finalization機制,輕量是因為Cleaner不是JVM建立的,不需要借助JNI調用建立,執行資源清理的代碼是ReferenceHandler Thread調用的而非finalizer Thread;健壯是因為Cleaner繼承自PhantomReference,是最弱的一種引用類型,可以避免惡心的順序問題。Cleaner封裝了執行資源清理任務的邏輯,具體的資源清理邏輯通過建立Cleaner時的方法入參Runnable方法指定,Cleaner保證執行資源清理任務是線程安全的,即會捕獲所有的異常,且保證隻執行一次。一旦垃圾回收器發現Cleaner執行個體是phantom-reachable,即沒有其他執行個體強引用該執行個體,垃圾回收器就會把Cleaner執行個體加入到Reference的pending隊列中,由ReferenceHandler Thread負責調用其clean方法執行資源清理動作。Cleaner并不能完全替代Object的finalization機制,使用Cleaner時要求其資源清理邏輯比較簡單,否則容易阻塞ReferenceHandler Thread,阻塞其他的資源清理任務執行。其實作如下:

public class Cleaner
    extends PhantomReference<Object>
{

    // 因為PhantomReference的構造方法要求必須傳入ReferenceQueue參數,是以這裡聲明了一個,但是實際上并不會往裡面添加Cleaner執行個體
    //因為ReferenceHandler Thread會直接調用Cleaner執行個體的clean方法,不會将其加入到dummyQueue隊列中
    private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue<>();

    //Cleaner連結清單的連結清單頭
    static private Cleaner first = null;

    private Cleaner
        next = null,
        prev = null;

    //add方法将cl加入到連結清單的頭部
    private static synchronized Cleaner add(Cleaner cl) {
        if (first != null) {
            cl.next = first;
            first.prev = cl;
        }
        first = cl;
        return cl;
    }

    //remove方法将cl從連結清單中移除
    private static synchronized boolean remove(Cleaner cl) {

        //說明cl已經從連結清單移除了,不需要再處理
        if (cl.next == cl)
            return false;

        // Update list
        if (first == cl) {
            if (cl.next != null)
                first = cl.next;
            else
                first = cl.prev;
        }
        if (cl.next != null)
            cl.next.prev = cl.prev;
        if (cl.prev != null)
            cl.prev.next = cl.next;

        //從連結清單移除後會将cl的prev和next都指向它自己
        cl.next = cl;
        cl.prev = cl;
        return true;

    }

    private final Runnable thunk;

    private Cleaner(Object referent, Runnable thunk) {
        super(referent, dummyQueue);
        this.thunk = thunk;
    }

    /**
     * 核心入口方法,建立Cleaner,thunk就是具體的執行資源清理的邏輯
     */
    public static Cleaner create(Object ob, Runnable thunk) {
        if (thunk == null)
            return null;
        return add(new Cleaner(ob, thunk));
    }

    /**
     * Runs this cleaner, if it has not been run before.
     */
    public void clean() {
        //首先從隊列移除目前Cleaner執行個體
        if (!remove(this))
            return;
        try {
            //執行資源清理
            thunk.run();
        } catch (final Throwable x) {
            //捕獲所有異常,列印err日志并退出
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        if (System.err != null)
                            new Error("Cleaner terminated abnormally", x)
                                .printStackTrace();
                        System.exit(1);
                        return null;
                    }});
        }
    }

}
           

Cleaner通過prev,next屬性内部維護了一個雙向連結清單,其中靜态屬性first就是連結清單頭,即所有的Cleaner執行個體通過連結清單維護着引用關系,但是這種引用是phantom-reachable的,一旦某個Cleaner執行個體沒有強引用則會被垃圾回收器加入到Reference的pending隊列中,等待被處理。Cleaner的核心方法有兩個,一是建立Cleaner執行個體的create方法,該方法會将建立的執行個體加入到連結清單中;另外一個是執行資源清理的clean方法,該方法将目前執行個體從連結清單中移除,然後執行Cleaner執行個體建立時傳入的thunk,如果出現異常則列印err日志并導緻JVM程序終止。

可以參考java.nio.DirectByteBuffer類中Cleaner的應用,如下:

Hotspot 對象引用Reference和Finalizer 源碼解析

cleaner是該類的一個私有屬性,在構造函數中初始化,當DirectByteBuffer執行個體被垃圾回收器回收掉了,這個cleaner屬性對應的Cleaner執行個體就沒有其他強引用了,隻剩下Cleaner内部維護的連結清單對其的虛引用了,就會被垃圾回收器加入到Reference的pending隊列中,等待被處理。

二、ReferenceQueue

1、定義

     ReferenceQueue主要用來通知Reference執行個體的使用方Reference執行個體對應的referent對象已經被回收掉了,允許使用方對Reference執行個體本身做适當的處理。注意ReferenceQueue本身并不直接持有Reference執行個體的引用,如果Reference執行個體本身變得不可達了,則無論Reference執行個體對應的referent對象被回收掉了,Reference執行個體都不會被添加到ReferenceQueue中。

ReferenceQueue包含的屬性如下:

  • static ReferenceQueue<Object> NULL = new Null<>();  //如果Reference執行個體的queue等于NULL,則表示該執行個體已經從隊列中移除
  • static ReferenceQueue<Object> ENQUEUED = new Null<>(); // //如果Reference執行個體的queue等于ENQUEUED,則表示該執行個體已經加入到隊列中
  • Lock lock = new Lock(); //改寫隊列的鎖
  • volatile Reference<? extends T> head = null;  //Reference連結清單的頭部
  • long queueLength = 0; //表示Reference連結清單的長度

上面的Null 和Lock都是ReferenceQueue的内部類,如下:

private static class Null<S> extends ReferenceQueue<S> {
        boolean enqueue(Reference<? extends S> r) {
            return false;
        }
    }
 
static private class Lock { };
           

 ReferenceQueue跟正常的隊列實作不同,ReferenceQueue依賴于Reference的next屬性構成一個連結清單,連結清單頭就是ReferenceQueue的靜态head屬性,加入到隊列中實際就是插入到連結清單的頭部。當Reference執行個體加入到ReferenceQueue中,Reference執行個體變成新的連結清單頭,next屬性就指向原來的連結清單頭,queue屬性變成ENQUEUED,相關邏輯在enqueue方法中;當Reference執行個體從ReferenceQueue中移除時,next屬性被重置為自己,原來的next屬性變成新的連結清單頭,queue屬性變成NULL,相關邏輯在reallyPoll方法中。重點關注以下方法的實作。

2、  enqueue / reallyPoll

       enqueue方法将Reference執行個體加入到連結清單的頭部,reallyPoll方法移除并傳回連結清單的頭部,這兩方法都要求擷取lock屬性的鎖,其中enqueue是public方法,reallyPoll方法是private方法。具體實作如下:

//enqueue方法将某個Reference執行個體加入到隊列中
    boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */
        synchronized (lock) {
            // Check that since getting the lock this reference hasn't already been
            // enqueued (and even then removed)
            ReferenceQueue<?> queue = r.queue;
            if ((queue == NULL) || (queue == ENQUEUED)) {
                return false;
            }
            assert queue == this;
            //将r插入到連結清單的頭部,r的queue置為ENQUEUED,表示其已經加入到隊列中,r的next屬性置為它自己
            r.queue = ENQUEUED;
            //插入第一個元素時,head等于null,此時r的next屬性就是r,插入以後的元素時,next屬性就是head
            r.next = (head == null) ? r : head;
            head = r;
            queueLength++;
            if (r instanceof FinalReference) {
                //增加FinalReference的計數
                sun.misc.VM.addFinalRefCount(1);
            }
            //喚醒其他的等待線程
            lock.notifyAll();
            return true;
        }
    }

    private Reference<? extends T> reallyPoll() {       /* Must hold lock */
        Reference<? extends T> r = head;
        if (r != null) {
            @SuppressWarnings("unchecked")
            Reference<? extends T> rn = r.next;
            //将連結清單頭從連結清單中移除,移除的Reference執行個體的queue會被置為NULL,next置為它自己
            head = (rn == r) ? null : rn;
            r.queue = NULL;
            r.next = r;
            queueLength--;
            if (r instanceof FinalReference) {
                //FinalReference的計數器減1
                sun.misc.VM.addFinalRefCount(-1);
            }
            return r;
        }
        return null;
    }
           

3、poll / remove 

     這兩方法都是移除并傳回連結清單的頭元素,差別在于poll方法不會阻塞,立即傳回null,remove方法會阻塞目前線程,直到目前線程擷取了一個Reference執行個體或者累計等待時間超過了指定時間,remove方法還有一個沒有參數的重載版本,會阻塞目前線程,直到目前線程被喚醒,等待時間無限制,如果被喚醒了連結清單頭還是null則傳回null,即隻等待一次。其實作如下:

/**
     *  從目前隊列中移除并傳回連結清單頭元素,如果為空則傳回null,WeakHashMap中就是調用此方法周遊隊列中所有的Reference執行個體
     */
    public Reference<? extends T> poll() {
        if (head == null)
            //隊列為空,傳回null
            return null;
        synchronized (lock) {
            //擷取隊列的鎖,移除并傳回連結清單頭元素
            return reallyPoll();
        }
    }

    /**
     * 移除并傳回隊列的頭元素,最多等待timeout,如果timeout等于0,則隻等待一次直到線程被喚醒,等待時間無限制
     */
    public Reference<? extends T> remove(long timeout)
        throws IllegalArgumentException, InterruptedException
    {
        if (timeout < 0) {
            throw new IllegalArgumentException("Negative timeout value");
        }
        synchronized (lock) {
            Reference<? extends T> r = reallyPoll();
            if (r != null) return r;
            long start = (timeout == 0) ? 0 : System.nanoTime();
            //相當于while死循環
            for (;;) {
                //等待最多timeout,如果timeout為0,則等待其他線程調用notify或者notifyAll
                lock.wait(timeout);
                //移除并傳回連結清單頭部元素
                r = reallyPoll();
                if (r != null) return r;
                if (timeout != 0) {
                    //檢查是否等待逾時
                    long end = System.nanoTime();
                    //如果timeout為0,則start為0,timeout算出來的就是一個負值,會立即傳回null,即隻wait一次
                    //如果timeout不為0,則可能wait多次,直到多次wait的累計時間大于設定的值,傳回null
                    timeout -= (end - start) / 1000_000;
                    if (timeout <= 0) return null;
                    //繼續下一次wait
                    start = end;
                }
            }
        }
    }

    /**
     * remove(long timeout)的重載版本,timeout固定傳0
     */
    public Reference<? extends T> remove() throws InterruptedException {
        return remove(0);
    }
           

4、forEach

      forEach方法用于周遊連結清單中的所有的Reference執行個體,通常用于調試目的,要求調用方不能保持對Reference執行個體的引用,避免影響其正常銷毀,其實作如下:

/**
       用來周遊隊列中所有Reference執行個體,通常用于調試目的,要求調用方不能保持對Reference執行個體的引用,避免影響其正常銷毀
     */
    void forEach(Consumer<? super Reference<? extends T>> action) {
        //注意執行forEach時不要求擷取鎖,是以讀取的元素可能已經從隊列中移除了
        for (Reference<? extends T> r = head; r != null;) {
            action.accept(r);
            @SuppressWarnings("unchecked")
            Reference<? extends T> rn = r.next;
            if (rn == r) {
                if (r.queue == ENQUEUED) {
                    //說明已經周遊到隊列最後一個元素,将r置為null
                    r = null;
                } else {
                    //r.queue等于NULL,說明r已經從隊列中移除了,需要從head開始重新周遊,以後r後面多個元素都可能被移除了,而且
                    //此時也無法擷取下一個周遊元素的引用
                    r = head;
                }
            } else {
                // next in chain
                r = rn;
            }
        }
    }
           

三、Finalizer

1、定義

       Finalizer繼承自FinalReference,FinalReference是一種特殊的引用類型,主要用來輔助實作Object finalization機制,其定義如下:

Hotspot 對象引用Reference和Finalizer 源碼解析

Finalizer定義的屬性如下:

  • static ReferenceQueue<Object> queue = new ReferenceQueue<>(); //全局的ReferenceQueue隊列
  • static Finalizer unfinalized = null; //所有Finalizer 執行個體構成的連結清單的頭元素
  • static final Object lock = new Object(); //修改連結清單的鎖
  • private Finalizer next = null, prev = null; //用來構成連結清單的表示下一個和上一個元素

     Finalizer是借助垃圾回收器對Reference執行個體的特殊處理機制實作的,每建立一個實作了finalize方法的對象時,JVM會通過調用Finalizer的register方法建立一個新的Finalizer執行個體,該對象就是Finalizer執行個體的referent對象,所有的Finalizer執行個體構成一個連結清單。當某個對象隻被Finalizer執行個體所引用,則将對應的Finalizer執行個體加入到Reference維護的pending連結清單中,通過ReferenceHandler Thread将pending連結清單中的Finalizer執行個體加入到Finalizer定義的全局ReferenceQueue中。Finalizer自身會另外起一個新線程,FinalizerThread,不斷的從全局的ReferenceQueue中取出帶出來的Finalizer執行個體,然後将該執行個體從Finalizer連結清單中移除,最後調用對應對象的finalize方法執行資源的清理,并将對referent對象的引用置為null,保證該對象能夠會回收掉。當JVM程序即将退出,JVM會通過java.lang.Runtime另起線程處理掉全局ReferenceQueue中未處理完的Finalizer執行個體,通過java.lang.Shutdown另起線程處理掉Finalizer連結清單中的Finalizer執行個體,即沒有加入到Reference維護的pending連結清單中的Finalizer執行個體。重點關注以下方法的實作。

2、register

      register是JVM建立對象時,如果該類實作了finalize方法,則以新建立的對象作為參數調用此方法建立一個Finalizer執行個體,并将其加入到Finalizer連結清單的頭部,其實作如下:

/*在對象建立的時候由JVM調用 */
static void register(Object finalizee) {
        new Finalizer(finalizee);
    }

private Finalizer(Object finalizee) {
        super(finalizee, queue);
        //執行構造方法的時候,會将目前執行個體加入到連結清單中
        add();
 }

private void add() {
        synchronized (lock) {
            //擷取鎖,将this插入到連結清單的頭部
            if (unfinalized != null) {
                this.next = unfinalized;
                unfinalized.prev = this;
            }
            unfinalized = this;
        }
    }
           

3、FinalizerThread

      FinalizerThread就是一個不斷循環的線程任務,從queue屬性中擷取待處理的Finalizer執行個體,将該執行個體從Finalizer連結清單中移除然後調用其finalize方法,最後将Finalizer執行個體對referent對象的引用置為null,進而保證GC能夠正确回收該對象,其實作如下:

private static class FinalizerThread extends Thread {
        //标記運作狀态
        private volatile boolean running;
        
        FinalizerThread(ThreadGroup g) {
            super(g, "Finalizer");
        }
        public void run() {
            //已運作
            if (running)
                return;

            //isBooted傳回false表示JVM未初始化完成
            while (!VM.isBooted()) {
                try {
                    //等待JVM初始化完成
                    VM.awaitBooted();
                } catch (InterruptedException x) {
                    // ignore and continue
                }
            }
            final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
            running = true;
            for (;;) {
                try {
                    //移除并傳回連結清單頭元素,如果為空則等待
                    Finalizer f = (Finalizer)queue.remove();
                    //執行Finalizer任務
                    f.runFinalizer(jla);
                } catch (InterruptedException x) {
                    // ignore and continue
                }
            }
        }
    }

private void runFinalizer(JavaLangAccess jla) {
        synchronized (this) {
            //hasBeenFinalized傳回true,說明該元素已經從隊列移除了,直接傳回
            if (hasBeenFinalized()) return;
            //将目前執行個體從隊列中移除
            remove();
        }
        try {
            //擷取所引用的對象referent
            Object finalizee = this.get();
            if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {
                //實際調用Object的finalize方法
                jla.invokeFinalize(finalizee);
                /* 去掉對finalizee的引用,讓GC回收掉該執行個體 */
                finalizee = null;
            }
        } catch (Throwable x) { }
        //将所引用的對象referent置為null,即去掉對referent的引用,讓GC回收掉該執行個體
        super.clear();
    }

private boolean hasBeenFinalized() {
        //next等于this說明該執行個體已經從連結清單中移除了,已經執行過Finalized方法了
        return (next == this);
    }


private void remove() {
        synchronized (lock) {
           //擷取鎖,将this從連結清單中移除
            if (unfinalized == this) {
                if (this.next != null) {
                    unfinalized = this.next;
                } else {
                    unfinalized = this.prev;
                }
            }
            if (this.next != null) {
                this.next.prev = this.prev;
            }
            if (this.prev != null) {
                this.prev.next = this.next;
            }
            //将next和prev都指向自己,表示已經從連結清單中移除
            this.next = this;   /* Indicates that this has been finalized */
            this.prev = this;
        }
    }
           

FinalizerThread是通過靜态static塊啟動的,如下:

static {
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        for (ThreadGroup tgn = tg;
             tgn != null;
             tg = tgn, tgn = tg.getParent());
        Thread finalizer = new FinalizerThread(tg);
        finalizer.setPriority(Thread.MAX_PRIORITY - 2);
        finalizer.setDaemon(true);
        finalizer.start();
    }
           

 上述jla.invokeFinalize(finalizee)方法的實作如下:

Hotspot 對象引用Reference和Finalizer 源碼解析

可以搜尋SharedSecrets.setJavaLangAccess方法的調用鍊找到上述實作,如下:

Hotspot 對象引用Reference和Finalizer 源碼解析

 4、runFinalization / runAllFinalizers

      runFinalization由Runtime.runFinalization()方法調用,負責清理掉queue中未處理的Finalizer執行個體;runAllFinalizers由java.lang.Shutdown,負責清理掉Finalizer連結清單中剩餘的即未加入到queue中的Finalizer執行個體;兩者都是調用forkSecondaryFinalizer方法執行清理任務,該方法會在系統線程組下另起一個線程執行指定任務,并等待該線程執行完成,如果執行異常,則終止目前線程。其實作如下:

/* Called by Runtime.runFinalization(),執行queue中未處理的Finalizer */
    static void runFinalization() {
        if (!VM.isBooted()) {
            return;
        }

        forkSecondaryFinalizer(new Runnable() {
            private volatile boolean running;
            public void run() {
                // in case of recursive call to run()
                if (running)
                    return;
                final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
                running = true;
                for (;;) {
                    //不斷周遊queue中所有的Finalizer,然後執行finalize方法
                    Finalizer f = (Finalizer)queue.poll();
                    if (f == null) break;
                    f.runFinalizer(jla);
                }
            }
        });
    }

    /* Invoked by java.lang.Shutdown,執行未加入到queue中的Finalizer */
    static void runAllFinalizers() {
        if (!VM.isBooted()) {
            return;
        }

        forkSecondaryFinalizer(new Runnable() {
            private volatile boolean running;
            public void run() {
                // in case of recursive call to run()
                if (running)
                    return;
                final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
                running = true;
                for (;;) {
                    //不斷周遊unfinalized連結清單中的元素,執行finalize方法
                    Finalizer f;
                    synchronized (lock) {
                        f = unfinalized;
                        if (f == null) break;
                        unfinalized = f.next;
                    }
                    f.runFinalizer(jla);
                }}});
    }

/* 
       在系統線程組下建立一個新的線程執行指定任務,并等待任務執行完成,之是以開啟一個新的線程,是為了與已經死鎖了或者停頓的finalizer thread隔離開來
       加速finalize方法的執行
     */
    private static void forkSecondaryFinalizer(final Runnable proc) {
        AccessController.doPrivileged(
            new PrivilegedAction<Void>() {
                public Void run() {
                    ThreadGroup tg = Thread.currentThread().getThreadGroup();
                    //從目前線程的線程組往上周遊找到最初的系統線程組
                    for (ThreadGroup tgn = tg;
                         tgn != null;
                         tg = tgn, tgn = tg.getParent());
                    //啟動一個新線程執行proc任務    
                    Thread sft = new Thread(tg, proc, "Secondary finalizer");
                    sft.start();
                    try {
                        //等待proc任務執行完成
                        sft.join();
                    } catch (InterruptedException x) {
                         //執行異常,中斷目前線程
                        Thread.currentThread().interrupt();
                    }
                    return null;
                }});
    }
           

5、InstanceKlass::register_finalizer

      該方法就是JVM中調用Finalizer的register方法的具體實作了,如下:

instanceOop InstanceKlass::register_finalizer(instanceOop i, TRAPS) {
  if (TraceFinalizerRegistration) {
    //列印跟蹤日志
    tty->print("Registered ");
    i->print_value_on(tty);
    tty->print_cr(" (" INTPTR_FORMAT ") as finalizable", (address)i);
  }
  //i是新建立的對象
  instanceHandle h_i(THREAD, i);
  //result表示調用結果
  JavaValue result(T_VOID);
  //args表示方法參數
  JavaCallArguments args(h_i);
  //擷取調用方法
  methodHandle mh (THREAD, Universe::finalizer_register_method());
  //執行方法調用
  JavaCalls::call(&result, mh, &args, CHECK_NULL);
  return h_i();
}

static Method*      finalizer_register_method()     { return _finalizer_register_cache->get_method(); }
           

  _finalizer_register_cache的初始化在Universe的universe_post_init方法中,如下:

Hotspot 對象引用Reference和Finalizer 源碼解析

詳細代碼說明可以參考《Hotspot 記憶體管理之Universe 源碼解析》 。

register_finalizer方法的調用鍊如下:

Hotspot 對象引用Reference和Finalizer 源碼解析

InstanceKlass::allocate_instance就是根據Class建立對象的底層實作,可以參考《Hotspot Java對象建立和TLAB源碼解析》;另外幾個Runtime結尾的是給位元組碼的彙編指令或者編譯器的編譯代碼使用的方法,最終的調用場景一樣是建立對象。 

繼續閱讀