【JAVA Reference】Cleaner 對比 finalize 對比 AutoCloseable(四)

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


一、Finalize

效果等同 Cleaner,都是在 GC 前搞點事情。

public class _05_01_TestCleanerFinalize {
    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++);
        }
    }

    @Data
    @AllArgsConstructor
    @ToString
    static class DemoObject {
        private String name;
//        private static final List<Object> objectList = Lists.newArrayList();

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

輸出:

demo0 running DoSomething ...
demo1 running DoSomething ...
demo2 running DoSomething ...
demo3 running DoSomething ...
demo4 running DoSomething ...
...

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

1.1 Finalize 讓要GC的對象復活

public class _05_01_TestCleanerFinalize {
    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++);
        }
    }

    @Data
    @AllArgsConstructor
    @ToString
    static class DemoObject {
        private String name;
        private static final List<Object> objectList = Lists.newArrayList();

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

輸出:

demo1 running DoSomething ...
2
[_05_01_TestCleanerFinalize.DemoObject(name=demo0), _05_01_TestCleanerFinalize.DemoObject(name=demo1)]
demo2 running DoSomething ...
3
[_05_01_TestCleanerFinalize.DemoObject(name=demo0), _05_01_TestCleanerFinalize.DemoObject(name=demo1), _05_01_TestCleanerFinalize.DemoObject(name=demo2)]
demo3 running DoSomething ...
4
[_05_01_TestCleanerFinalize.DemoObject(name=demo0), _05_01_TestCleanerFinalize.DemoObject(name=demo1), _05_01_TestCleanerFinalize.DemoObject(name=demo2), _05_01_TestCleanerFinalize.DemoObject(name=demo3)]
demo4 running DoSomething ...
5
[_05_01_TestCleanerFinalize.DemoObject(name=demo0), _05_01_TestCleanerFinalize.DemoObject(name=demo1), _05_01_TestCleanerFinalize.DemoObject(name=demo2), _05_01_TestCleanerFinalize.DemoObject(name=demo3), _05_01_TestCleanerFinalize.DemoObject(name=demo4)]
...

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

1.2 防止子類亂來(需要的話)

把全部 finalize 都定義成final,不讓繼承修改。

二、 Cleaner 對比 Finalize

2.1 不同點

2.1.1 線程優先級: Finalize 低優先級。 Cleaner 屬於高優先級。

那麼造成的後果就是,大量使用 Finalize 的話,假如有成千上萬的對象在等待 Finalize 方法執行去釋放資源,在cpu繁忙的情況下,沒空去調用 Finalize 方法,那麼有可能 OutOfMemoryError 使得 JVM死掉。在這裏插入圖片描述
=== 點擊查看top目錄 ===

2.1.2 finalizer中拋出的異常,無感知,Cleaner 沒這個問題
public class _05_02_TestCleanerVsFinalize {
    public static void main(String[] args) throws Exception {

//        testFinalizeException();
        testCleanerException();

    }

    private static void testCleanerException() throws Exception{
        int index = 0;
        while (true) {
            Thread.sleep(1000);
            // 提醒 GC 去進行垃圾收集了
            System.gc();

            // 該對象不斷重新指向其他地方,那麼原先指針指向的對象的就屬於需要回收的數據
            Object obj = new Object();
            /*
                 增加 obj 的虛引用,定義清理的接口 DoSomethingThread
                 第一個參數:需要監控的堆內存對象
                 第二個參數:程序釋放資源前的回調。
             */
            Cleaner.create(obj, new DoSomethingThread("thread_" + index++));
        }
    }

    private static void testFinalizeException() throws Exception{
        int index = 0;
        while (true) {
            Thread.sleep(1000);
            // 提醒 GC 去進行垃圾收集了
            System.gc();

            // 該對象不斷重新指向其他地方,那麼原先指針指向的對象的就屬於需要回收的數據
            DemoObject obj = new DemoObject("demo" + index++);
            System.out.println(" running DoSomething ...");
            System.out.println(10 / 0);
            //這個地方死了,上面有異常,不走到這,可是確連個異常都不跑出來
            System.out.println(" running DoSomething End ...");
        }
    }

    @Data
    @AllArgsConstructor
    @ToString
    static class DemoObject {
        private String name;
        @Override
        protected void finalize() throws Throwable {
            System.out.println(name + " running DoSomething ...");
            System.out.println(10 / 0);
            //這個地方死了,上面有異常,不走到這,可是確連個異常都不打印到出來
            System.out.println(name + " running DoSomething End ...");
        }
    }

    static class DoSomethingThread implements Runnable {
        private String name;

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

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

結論:finalize() 方法內部 10/0,出了問題,但是日誌不打出來,程序也不往下走。
爲什麼會這樣?Finalizer 剖析 (六)#4.9.3.1

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

2.2 相同點

2.2.1 都無法保證執行時間,對於時間敏感的操作,不允許利用這兩者處理。

它甚至不能能保證它們是否會運行。當一個程序結束後,一些不可達對象上的Finalizer和Cleaner機制仍然沒有運行。因此,不應該依賴於Finalizer和Cleaner機制來更新持久化狀態。要是你靠這兩個機制來釋放分佈式鎖,那你就等着完蛋吧。
也不要期待以下方法爲finalizer的執行提供保障:

System.gc();

System.runFinalization();

Runtime.runFinalizersOnExit(true);

System.runFinalizersOnExit(true);

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

2.2.2 都可以作爲最後一道防線(安全網 safenet)

在這裏插入圖片描述
InputStream內的 finalize 方法,用於關閉鏈接。但是由於GC時間不確定,所以關閉的時間也不確定,假如有一堆IO忘記關閉,還是會造成連接數上限的情況,這點要注意。
=== 點擊查看top目錄 ===

2.2.3 都會影響性能

使用finalizer和cleaner機制會導致嚴重的性能損失。 在我的機器上,創建一個簡單的AutoCloseable對象,使用try-with-resources關閉它,並讓垃圾回收器回收它的時間大約是12納秒。 使用finalizer機制,而時間增加到550納秒。 換句話說,使用finalizer機制創建和銷燬對象的速度要慢50倍。 這主要是因爲finalizer機制會阻礙有效的垃圾收集。
=== 點擊查看top目錄 ===

三、AutoCloseable 接口

// @since 1.7
public interface AutoCloseable {
    void close() throws Exception;
}

3.1 AutoCloseable 使用實戰

3.1.1 傳統調用方法
public class _06_00_TestAutoCloseable{
    public static void main(String[] args) {
        _06_00_TestAutoCloseable instance = new _06_00_TestAutoCloseable();
        try{
            instance.doIt();
        }finally {
            instance.close();
        }
    }
    public void doIt() { System.out.println("MyAutoClosable doing it!"); }
    public void close() { System.out.println("MyAutoClosable closed!"); }
}

輸出:

MyAutoClosable doing it!
MyAutoClosable closed!
3.1.2 繼承 AutoCloseable ,使用try-with-resource 語法糖
public class _06_01_TestAutoCloseable implements AutoCloseable {

    public static void main(String[] args) {
        try(_06_01_TestAutoCloseable instance = new _06_01_TestAutoCloseable()){
            instance.doIt();
        }
    }

    public void doIt() { System.out.println("MyAutoClosable doing it!"); }

    @Override
    public void close() {
        System.out.println("MyAutoClosable closed!");
    }
}

輸出:

MyAutoClosable doing it!
MyAutoClosable closed!

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

3.1.3 文件IO中的實戰使用
  • 使用 try-with-resource
public class _06_02_TestAutoCloseable {
    public static void main(String[] args){
        try(BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("test.txt")));
                BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")))){
            int b;
            while ((b = bin.read()) != -1) {
                bout.write(b);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

BufferedInputStream 與 BufferedOutputStream 都實現了 AutoCloseable

四、 lombok 的 @Cleanup

跟 try-with-resource 效果一樣。

4.1 Cleanup 使用實戰

4.1.1 demo
public class _06_02_TestAutoCloseable implements AutoCloseable {

    public static void main(String[] args) {
        @Cleanup
        _06_01_TestAutoCloseable instance = new _06_01_TestAutoCloseable();
        instance.doIt();

    }
    public void doIt() { System.out.println("MyAutoClosable doing it!"); }

    @Override
    public void close() {
        System.out.println("MyAutoClosable closed!");
    }
}

輸出:

MyAutoClosable doing it!
MyAutoClosable closed!
4.1.2 文件IO中的實戰使用
  • 使用 @Cleanup
public class _06_04_TestAutoCloseable {
    public static void main(String[] args) throws Exception {
        @Cleanup BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("test.txt")));
        @Cleanup BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")));
        int b;
        while ((b = bin.read()) != -1){
            bout.write(b);
        }
    }
}

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

五、總結

  1. 儘量避免使用 Cleaner 與 finalize

六、番外篇

下一章節:【JAVA Reference】Cleaner 在堆外內存DirectByteBuffer中的應用(五)
上一章節:【JAVA Reference】Cleaner 源碼剖析(三)

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