特殊的引用-FinalReference

你們都知道java裏面引用有SoftReference、WeakReference、PhantomReference,他們都繼承自抽象類Reference,咱們看一下他的類圖: java

能夠發現,除了最熟悉的強引用沒有對應的Reference實現外,虛引用,弱引用和軟引用都有對應的Reference實現類。

那麼,多出來的FinalReference實現是幹什麼的呢? bash

FinalReference

能夠看到,FinalReference類僅僅是繼承了Reference類而已。jvm

/**
 * Final references, used to implement finalization
 */
class FinalReference<T> extends Reference<T> {

    public FinalReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}
複製代碼

註釋中說他是用來實現finalization(終結)的。post

其真正的邏輯位於FinalReference的惟一子類:java.lang.ref.Finalizer中。ui

注意,該類爲包級私有,有final關鍵字修飾,且構造方法爲private,提供了register方法供JVM調用。this

構造方法以及register方法

final class Finalizer extends FinalReference<Object> { 
    //...
    
    private Finalizer(Object finalizee) {
        super(finalizee, queue);
        add();
    }
    /* Invoked by VM */
    static void register(Object finalizee) {
        new Finalizer(finalizee);
    }
}
複製代碼

由於register方法並無返回值,因此在外部是沒法獲取到建立的Finalizer對象。 其中,構造方法中調用的super(finalizee, queue)會將入參finalizee加入到引用隊列queue中。spa

關於引用隊列,見juejin.im/post/5e19d6…線程

咱們分析的轉入構造方法中所調用的add方法。code

private static ReferenceQueue<Object> queue = new ReferenceQueue<>();
    private static Finalizer unfinalized = null;
    private static final Object lock = new Object();

    private Finalizer
        next = null,
        prev = null;
    //...
    private void add() {
        synchronized (lock) {
            if (unfinalized != null) {
                this.next = unfinalized;
                unfinalized.prev = this;
            }
            unfinalized = this;
        }
    }
複製代碼

結合nextprev屬性和add方法,能夠比較容易的看出unfinalized其實是一個雙向鏈表,在add方法被調用後,就會將當前對象加入到unfinalized鏈表。cdn

其實,在構造方法方法被調用後,實際上作了以下兩件事:

  • 調用super,將入參對象註冊至引用隊列。
  • 調用add方法,將當前建立對象加入unfinalized鏈表。

由於register方法並無返回值,且unfinalized屬性爲靜態成員變量,因此當前建立對象在虛擬機內僅該unfinalized鏈表持有一份引用

根據註釋和訪問規則來看,register方法僅會被虛擬機所調用,並且,只有重寫了java.lang.Object#finalize方法的類纔會被做爲參數調用Finalizer#register方法。

後臺線程

與pending handler相似,在FinalReference中一樣也是使用靜態代碼塊來啓動後臺線程。

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();
    }
複製代碼

看一下FinalizerThread類,該類繼承了Thread類,並重寫run方法。

private static class FinalizerThread extends Thread {
        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 (;;) {
                try {
                    Finalizer f = (Finalizer)queue.remove();
                    f.runFinalizer(jla);
                } catch (InterruptedException x) {
                    // ignore and continue
                }
            }
        }
    }
複製代碼

上面代碼段中僅保留了關鍵流程代碼。

能夠看出在run方法內使用了一個死循環,每次循環先將隊首元素從引用隊列中取出(在構造方法內將對象註冊至引用隊列,當引用狀態變爲pending時,會由Pending-handler-thread將其加入該註冊隊列),並執行runFinalizer方法。

繼續看runFinalizer方法:

private void runFinalizer(JavaLangAccess jla) {
        synchronized (this) {
            if (hasBeenFinalized()) return;
            remove();
        }
        try {
            Object finalizee = this.get();
            if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {
                jla.invokeFinalize(finalizee);

                /* Clear stack slot containing this variable, to decrease
                   the chances of false retention with a conservative GC */
                finalizee = null;
            }
        } catch (Throwable x) { }
        super.clear();
    }
複製代碼

根據方法名就能夠看出來,該方法的做用就是執行jla.invokeFinalize(finalizee),並執行一些清理操做。

而在JavaLangAccess的實現類中(java.lang.System中的一個匿名內部類),invokeFinalize的代碼也很是簡單,只是調用finalize方法。

public void invokeFinalize(Object o) throws Throwable {
        o.finalize();
    }
複製代碼

invokeFinalize以後,代碼中去主動將finalizee設置爲null,根據上面的註釋可知,是爲了清除該方法對當前對象的引用,減少影響gc的機率。

在執行finalizee方法時,該對象會被臨時加一個強引用,進而對gc產生影響

finalize方法

從上面的分析過程能夠看出java.lang.Object中的finalize方法在對象將要被回收的時由一個守護線程去調用他們的finalize方法。

因爲該線程的優先級並不能保證,在準備調用finalize方法到調用結束時,可能已經通過了屢次gc,而因爲臨時的強引用,致使該對象遲遲沒有被回收。

可是,finalize的調用並不能被保證。因此,該方法在java9已被標記爲過期。咱們也不該該去重寫該方法去作清理工做。

總結

其實FinalReference就是jdk爲了將Finalizer方法實現相似析構方法而打造的類。

由虛擬機先將重寫了Finalizer方法的對象註冊至引用隊列,暫存在鏈表中。

當對象引用狀態變爲Enqueued後,由守護線程從引用隊列中取出對象,創建臨時的強引用,並調用Finalizer方法。

因爲守護線程的優先級較低,並不能保證重寫的Finalizer方法在被回收前必定會被執行。而且由於有臨時強引用的存在,還可能使該對象錯過gc。

因此,並不該該使用Finalizer方法~

參考資料

jdk8源碼&doc

www.infoq.cn/article/jvm…

相關文章
相關標籤/搜索