【JAVA Reference】Finalizer 剖析 (六)

我的原則:先會用再說,內部慢慢來。
學以致用,根據場景學源碼


一、前言

建議提前閱讀:

  1. 【JAVA Reference】ReferenceQueue 與 Reference 源碼剖析(二)
  2. 【JAVA Reference】Cleaner 源碼剖析(三)
  3. 【JAVA Reference】Cleaner 對比 finalize 對比 AutoCloseable(四)

二、架構

2.1 代碼架構

在這裏插入圖片描述
在這裏插入圖片描述

  • Finalizer 繼承 FinalReference 再繼承 Reference。

2.2 UML流程圖

在這裏插入圖片描述

=== 點擊查看top目錄 ===

三、思考 Finalizer Vs Cleaner 放一起

3.1 代碼 demo

public class _05_03_TestCleanerWithFinalize {
    public static void main(String[] args) throws Exception {
        int index = 0;
        while (true) {
            Thread.sleep(1000);
            // 提醒 GC 去進行垃圾收集了
            System.gc();

            // 該對象不斷重新指向其他地方,那麼原先指針指向的對象的就屬於需要回收的數據
            DemoObject obj = new DemoObject("demo" + index++);
            Cleaner.create(obj, new CleanerTask("thread_" + index++));
        }
    }

    @Data
    @AllArgsConstructor
    @ToString
    static class DemoObject {
        private String name;

        @Override
        protected void finalize() throws Throwable {
            System.out.println("finalize running DoSomething ..." + name);
        }
    }

    static class CleanerTask implements Runnable {
        private String name;

        public CleanerTask(String name) {
            this.name = name;
        }

        // do something before gc
        @Override
        public void run() {
            System.out.println("CleanerTask running DoSomething ..." + name );
        }
    }
}

輸出:

finalize running DoSomething ...demo0
CleanerTask running DoSomething ...thread_1
finalize running DoSomething ...demo2
CleanerTask running DoSomething ...thread_3
finalize running DoSomething ...demo4
CleanerTask running DoSomething ...thread_5
finalize running DoSomething ...demo6
CleanerTask running DoSomething ...thread_7
finalize running DoSomething ...demo8
...

可以看到,每次 finalize 總是比 Cleaner 先執行,不管你run幾次,結果都一樣,那麼思考一下爲什麼?

=== 點擊查看top目錄 ===

3.2 爲什麼 finalize 總是比 Cleaner 先執行 ?

  • 結論先拋出來: Cleaner 和 finalize 內部都有指針Pointer 指向了即將要回收的 Object 對象,但是 Cleaner 底層是虛引用(PhamtonReference),而 finalize的底層是 Finalizer ,屬於強引用。所以,必須強引用的釋放完對象,才輪到 Cleaner。

=== 點擊查看top目錄 ===

四、Finalizer 源碼剖析

4.1 父類 FinalReference

class FinalReference<T> extends Reference<T> {
    public FinalReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}
  • 注意:這個類的修飾符是 default ,也就是隻能在包 java.lang.ref 內被使用 。
  • 由繼承關係可以得知,FinalReference 只有一個子類 Finalizer
    在這裏插入圖片描述

=== 點擊查看top目錄 ===

4.2 Finalizer 類

final class Finalizer extends FinalReference<Object> 
  • 注意,Finalizer 是個 final 類,也就是斷子絕孫類,不會有繼承,可以防止篡改與注入。
  • Finalizer 類的修飾符是 default ,也就是隻能在包 java.lang.ref 內被使用 。那麼總覽了一下,他是 JVM 調用的。

4.3 Finalizer 類變量

4.3.1 私有static變量 queue

Finalizer引用的對象被gc之前,jvm會把相應的Finalizer對象放入隊列 queue

    private static ReferenceQueue<Object> queue = new ReferenceQueue<>();

=== 關於Reference的四個狀態,可以看圖 ===
=== 點擊查看top目錄 ===

4.3.2 私有static變量 unfinalized

靜態的Finalizer對象鏈,每=== 實例化 ===一個對象,這個隊列就會插入=== add ===一個。

    // 靜態的Finalizer對象鏈
    private static Finalizer unfinalized = null;

=== 點擊查看top目錄 ===

4.3.3 next + prev 指針
    // 雙端指針
    private Finalizer
        next = null,
        prev = null;

=== 點擊查看top目錄 ===

4.3.3 私有 static final 變量 lock
    private static final Object lock = new Object();
  • 每次對 unfinalized 隊列進行 add 與 remove 的時候,都會進行加鎖。

=== 點擊查看top目錄 ===

4.4 對象初始化

4.4.1 構造方法
  • 私有構造方法,說明無法通過 new 實例化
private Finalizer(Object finalizee) {
		// 4.4.1 初始化
        super(finalizee, queue);
        // 4.5 往前面插入
        add();
    }
class FinalReference<T> extends Reference<T> {
    public FinalReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}

=== 點擊查看top目錄 ===

4.4.2 真正初始化對象的 register 方法
  • java.lang.ref.Finalizer#register
	/* Invoked by VM */
    static void register(Object finalizee) {
        new Finalizer(finalizee);
    }
  • 註冊 Finalizer 對象,只要是類 override Finalize 方法的,初始化類的對象後,都會被VM註冊到這裏來。
  • 參數 Object finalizee ,表示指針指向的對象。

=== 點擊查看top目錄 ===

4.5 add 方法

往隊列 unfinalized 的頭部插入

// 往頭部插
    private void add() {
        synchronized (lock) {
            if (unfinalized != null) {
                this.next = unfinalized;
                unfinalized.prev = this;
            }
            unfinalized = this;
        }
    }

=== 點擊查看top目錄 ===

4.6 remove 方法

往隊列 unfinalized 的頭部移除

    // 從頭部移除
    private void remove() {
        synchronized (lock) {
            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 都指向自己,表明已經被 remove 掉了,準備調用類重新複寫的 finalize 方法
            this.next = this;   /* Indicates that this has been finalized */
            this.prev = this;
        }
    }

=== 點擊查看top目錄 ===

4.7 hasBeenFinalized 方法

  • 是否已經執行了 finalize 方法,如果 this.next = this; this.prev = this; 那麼就是說明已經執行了。 因爲 runFinalizer 的內部調用了 remove 方法
    private boolean hasBeenFinalized() {
        return (next == this);
    }

=== 點擊查看top目錄 ===

4.8 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); // 特地讓出了優先級,這個跟cleaner還不一樣
        finalizer.setDaemon(true); // 幽靈線程,這個跟 cleaner 一樣
        finalizer.start(); // 把線程給跑起來
    }
  1. 啓動一個內部類 FinalizerThread 線程。
  2. 設置優先級是 Thread.MAX_PRIORITY - 2,Cleaner 的優先級是 MAX_PRIORITY。雖然Cleaner的優先級比 FinalizerThread 高,但是由於 FinalizerThread 有強引用,所以 Cleaner 還是比較晚地執行。
  3. setDaemon 幽靈線程,這個跟 cleaner 一樣

4.9 內部類 FinalizerThread

  • java.lang.ref.Finalizer.FinalizerThread 私有靜態內部類
private static class FinalizerThread extends Thread {
        // 這個參數用來判斷該線程是否已經啓動
        private volatile boolean running;
        FinalizerThread(ThreadGroup g) {
            super(g, "Finalizer");
        }
        public void run() {
            // in case of recursive call to run()
            // 如果發生了遞歸調用則直接返回
            if (running)
                return;

            // Finalizer thread starts before System.initializeSystemClass
            // is called.  Wait until JavaLangAccess is available
            // Finalizer線程在 System.initializeSystemClass 被調用前啓動
            // 需要等到JVM已經初始化完成才能執行
            // 4.9.1
            while (!VM.isBooted()) {
                // delay until VM completes initialization
                // 延遲等待 JVM 完全初始化完畢
                try {
                    VM.awaitBooted();
                } catch (InterruptedException x) {
                    // ignore and continue
                }
            }
            // 4.9.2
            final JavaLangAccess jla = SharedSecrets.getJavaLangAccess(); // getter 直接看 Reference 類的 static 代碼塊 
            running = true;
            // 4.9.3
            for (;;) {
                try {
                    // 將 Finalizer 從 queue 隊列中拿出來然後調用完方法後釋放
                    // Active -> pending -> enqueue -> 出隊列,進行處理
                    Finalizer f = (Finalizer)queue.remove();
                    // 調用其runFinalizer方法,實質是要去調用該對象覆寫的 finalize 方法
                    f.runFinalizer(jla);
                } catch (InterruptedException x) {
                    // ignore and continue
                    // 這個地方要注意,你重寫的 finalize 方法,裏面拋出的異常,是直接忽略的
                }
            }
        }
    }

=== 點擊查看top目錄 ===

4.9.1 VM.isBooted() 方法

判斷 JVM 是否已經啓動,如果還沒啓動,那麼 VM.awaitBooted() 等待 JVM 啓動再說。

=== 點擊查看top目錄 ===

4.9.2 SharedSecrets.getJavaLangAccess();
  • setter 在 java.lang.System#setJavaLangAccess 方法內部

=== 點擊查看top目錄 ===

4.9.3 死循環執行 runFinalizer
for (;;) {
     try {
         // 將 Finalizer 從 queue 隊列中拿出來然後調用完方法後釋放
         // Active -> pending -> enqueue -> 出隊列,進行處理
         Finalizer f = (Finalizer)queue.remove();
         // 調用其runFinalizer方法,實質是要去調用該對象覆寫的 finalize 方法
         f.runFinalizer(jla);
     } catch (InterruptedException x) {
         // ignore and continue
         // 這個地方要注意,你重寫的 finalize 方法,裏面拋出的異常,是直接忽略的
     }
 }
  • queue.remove(); 把 queue 中的 Referece 全部拿出來,然後調用他們override的finalize方法。

=== 點擊查看top目錄 ===

4.9.3.1 注意一下 try-catch 喫掉異常
  • 這個地方直接就把異常喫掉了,那麼類定義的 finalize方法不管出現什麼情況,都不會有異常打印出來,也不會對異常做任何處理。(即使你調用了 10 / 0),=== 具體看代碼Demo#2.1.2 ===

=== 點擊查看top目錄 ===

4.10 runFinalizer 方法

  • java.lang.ref.Finalizer#runFinalizer
    // 執行 finalize 方法,然後進行GC回收
    private void runFinalizer(JavaLangAccess jla) {
        synchronized (this) {
            // 只能執行1次,如果已經執行了。那麼直接 return 回收
            if (hasBeenFinalized()) return;
            //執行 remove方法 ,從 unfinalized 鏈中拿掉該節點,然後返回出來,有點類似於 pop 操作
            remove(); 
        }
        try {
            // 拿到 指針指向的對象
            Object finalizee = this.get();
            if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {
                // 對象調用 finalize 方法
                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) { }
        // 4.9.5 解除強引用
        super.clear(); 
    }
  1. 先判斷是否已經執行過 finalize 方法了,是的話,就返回。所以記住,finalize方法只會執行一次。
  2. 調用 remove() 方法,把 next 和 prev都指向自己,所以剛剛方法hasBeenFinalized判斷是否已經處理過的依據,就是這麼來的。
  3. Object finalizee = this.get(); 記住了,這個是個強引用,因爲都是放在unfinalized隊列裏面的。
  4. jla.invokeFinalize(finalizee); 調用對象 finalizee 的 finalize方法。
  5. 局部變量 finalizee = null,指針 finalizee 指向了空,方便 GC

=== 點擊查看top目錄 ===

4.11 clear() 方法

  • java.lang.ref.Reference#clear
    public void clear() {
        this.referent = null;
    }

解除強引用。

=== 點擊查看top目錄 ===

4.12 總結一下

JVM啓動時,自動開啓兩個線程

  1. Finalizer 內部 static 塊啓動了FinalizerThread 線程會把 reference 從 queue 隊列中再拿出來,調用他的 finalize 方法,然後再去回收。
  2. 初始化 Finalizer 的父類 Reference 的 static 塊啓動了 ReferenceHandler 線程會不斷把 pending 隊列的 reference 丟到 queue 隊列,
    === 關於Reference的流程圖 ===

=== 點擊查看top目錄 ===

五、JVM負責調用的三方法

5.1 Register方法

  • 只要發現有哪個類複寫了finalize方法,那麼就會進行 register

5.2 runFinalization 方法

5.2.1 Runtime#runFinalization 方法
  • java.lang.Runtime#runFinalization 系統關閉時會調用
/* Wormhole for calling java.lang.ref.Finalizer.runFinalization */
private static native void runFinalization0();

public void runFinalization() {
        runFinalization0();
    }

=== 點擊查看top目錄 ===

5.2.2 Finalizer.runFinalization 方法
/* Called by Runtime.runFinalization() */
    static void runFinalization() {
    	// JVM 是否啓動
        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 (;;) {
                    Finalizer f = (Finalizer)queue.poll();
                    if (f == null) break;
                    f.runFinalizer(jla);
                }
            }
        });
    }
  • 把 referenceQueue 裏面的 Reference 都拿出來,然後調用 finalize 方法

=== 點擊查看top目錄 ===

5.2.3 forkSecondaryFinalizer 方法
  • 調用了AccessController.doPrivileged方法,這個方法的作用是使其內部的代碼段獲得更大的權限,可以在裏面訪問更多的資源。
    private static void forkSecondaryFinalizer(final Runnable proc) {
        // 調用了AccessController.doPrivileged方法,這個方法的作用是使其內部的代碼段獲得更大的權限,可以在裏面訪問更多的資源。
        AccessController.doPrivileged(
            new PrivilegedAction<Void>() {
                public Void run() {
                    ThreadGroup tg = Thread.currentThread().getThreadGroup();
                    for (ThreadGroup tgn = tg;
                         tgn != null;
                         tg = tgn, tgn = tg.getParent());
                    Thread sft = new Thread(tg, proc, "Secondary finalizer");
                    sft.start();
                    try {
                        sft.join();
                    } catch (InterruptedException x) {
                        Thread.currentThread().interrupt();
                    }
                    return null;
                }});
    }

=== 點擊查看top目錄 ===

5.3 runAllFinalizers 方法

5.3.1 Shutdown#runAllFinalizers 方法
  • java.lang.Shutdown#runAllFinalizers
    /* Wormhole for invoking java.lang.ref.Finalizer.runAllFinalizers */
    private static native void runAllFinalizers();

=== 點擊查看top目錄 ===

5.3.2 Finalizer.runAllFinalizers 方法
/* Invoked by java.lang.Shutdown */
    static void runAllFinalizers() {
    	// JVM 是否啓動
        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 (;;) {
                    Finalizer f;
                    synchronized (lock) {
                        f = unfinalized;
                        if (f == null) break;
                        unfinalized = f.next;
                    }
                    f.runFinalizer(jla);
                }}});
    }
  • 把 unfinalized 裏面的 Reference 都拿出來,然後調用 finalize 方法
  • 這裏加了一把鎖,因爲有進有出

=== 點擊查看top目錄 ===

5.4 總結一下

5.4.1 runAllFinalizers 與 runFinalization 的不同
/* Called by Runtime.runFinalization() */
static void runFinalization() {
...
                for (;;) {
                    Finalizer f = (Finalizer)queue.poll();
                    if (f == null) break;
                    f.runFinalizer(jla);
                }
...
    }
/* Invoked by java.lang.Shutdown */
    static void runAllFinalizers() {
...
                for (;;) {
                    Finalizer f;
                    synchronized (lock) {
                        f = unfinalized;
                        if (f == null) break;
                        unfinalized = f.next;
                    }
                    f.runFinalizer(jla);
                }
...
    }
  1. 前者從 referenceQueue 裏面拿 reference,也就是把 queue 隊列消耗光。後者從 unfinalized 隊列拿 reference,說明是要把全部已經實例化的對象(覆蓋了finalize方法的類的對象)拿出來。
  2. 注意 unfinalized 隊列裏面的 Reference 有可能還沒有被 GC,只是登記在冊而已,而queue隊列裏面的 reference 都是GC丟進來的了。
  3. Shutdown 打算關閉 JVM了,所以把暫時還沒被GC的也一起跑到最後,就像是網吧今天停止營業了,不管你上網多久(剛上網5分鐘的還有上網2小時快沒錢了準備走人的),都得結賬走人。

=== 點擊查看top目錄 ===

六、番外篇

上一章節:【JAVA Reference】Cleaner 在堆外內存DirectByteBuffer中的應用(五)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章