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

我的原则:先会用再说,内部慢慢来。
学以致用,根据场景学源码

文章目录

      • 一、Finalize
        • 1.1 Finalize 让要GC的对象复活
        • 1.2 防止子类乱来(需要的话)
      • 二、 Cleaner 对比 Finalize
        • 2.1 不同点
          • 2.1.1 线程优先级: Finalize 低优先级。 Cleaner 属于高优先级。
          • 2.1.2 finalizer中抛出的异常,无感知,Cleaner 没这个问题
        • 2.2 相同点
          • 2.2.1 都无法保证执行时间,对于时间敏感的操作,不允许利用这两者处理。
          • 2.2.2 都可以作为最后一道防线(安全网 safenet)
          • 2.2.3 都会影响性能
      • 三、AutoCloseable 接口
        • 3.1 AutoCloseable 使用实战
          • 3.1.1 传统调用方法
          • 3.1.2 继承 AutoCloseable ,使用try-with-resource 语法糖
          • 3.1.3 文件IO中的实战使用
      • 四、 lombok 的 @Cleanup
        • 4.1 Cleanup 使用实战
          • 4.1.1 demo
          • 4.1.2 文件IO中的实战使用
      • 五、总结
      • 六、番外篇


  • Clean 直接看上一章 【JAVA Reference】Cleaner 源码剖析(三)

一、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 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死掉。【JAVA Reference】Cleaner 对比 finalize 对比 AutoCloseable(四)_第1张图片
=== 点击查看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)

【JAVA Reference】Cleaner 对比 finalize 对比 AutoCloseable(四)_第2张图片
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

  • 看 UML 图
    【JAVA Reference】Cleaner 对比 finalize 对比 AutoCloseable(四)_第3张图片
    === 点击查看top目录 ===

四、 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 源码剖析(三)

你可能感兴趣的:(引用)