欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Effective Java 2.7——避免使用终结方法

程序员文章站 2024-01-10 21:33:37
...

第七条 避免使用终结方法

终结方法

这一条书中很直接的告诉我们:不要用终结方法。那么什么是终结方法呢?其实就是一个叫做finalize()的方法。
它位于Object类中
Effective Java 2.7——避免使用终结方法
具体谷歌官方是怎么表述的 我们直接把注释搬上来仔细看看吧(难得专业一回,翻译的不好大家不要介意哈~)

Called by the garbage collector on an object when garbage collection determines that there are no more references to the object. A subclass overrides the {@code finalize} method to dispose of system resources or to perform other cleanup.
当垃圾回收器因为发现一个对象没有一个有效的引用而决定回收对象时,这个方法会被垃圾回收器调用。子类重写finalize方法来处理系统资源或者来执行一些其他的清理工作。

The general contract of {@code finalize} is that it is invoked if and when the Java™ virtual machine has determined that there is no longer any means by which this object can be accessed by any thread that has not yet died, except as a result of an action taken by the finalization of some other object or class which is ready to be finalized. The {@code finalize} method may take any action, including making this object available again to other threads; the usual purpose of {@code finalize}, however, is to perform cleanup actions before the object is irrevocably discarded. For example, the finalize method for an object that represents an input/output connection might perform explicit I/O transactions to break the connection before the object is permanently discarded.
对于finalize方法总的规则是当java虚拟机发现除了一些被其他准备好被终结的对象或者类处理的对象处理结果带对象之外的一个在任何尚未死亡的线程中不能以任何的路径访问到的对象会被调用。finalize方法里面可以做任何事情包括使这个对象在其他线程中重新变得可见。然而在通常情况下finalize方法中做的事情是在这个对象在被不可撤销的删除之后做一些清理工作。比如:一个有输入输出连接的对象的终结方法可用来在对象被永久删除之前显示地断开I/O连接。

The {@code finalize} method of class {@code Object} performs no special action; it simply returns normally. Subclasses of {@code Object} may override this definition.
Object类的finalize方法没有作特殊的处理,它就很简单的一个正常返回。Object的子类可以来覆盖父类的这个定义。

The Java programming language does not guarantee which thread will invoke the {@code finalize} method for any given object. It is guaranteed, however, that the thread that invokes finalize will not be holding any user-visible synchronization locks when finalize is invoked. If an uncaught exception is thrown by the finalize method, the exception is ignored and finalization of that object terminates.
Java编程语言对于所有的对象都不能够保证哪个线程会调用它的finalize方法。然而可以保证一点,调用终结方法的线程必然不会被任何用户可见的同步锁锁住。如果一个没有被捕获的异常在执行终结方法时被抛出,异常会被忽略并且哪个对象的终结过程会被终止。

After the {@code finalize} method has been invoked for an object, no further action is taken until the Java virtual machine has again determined that there is no longer any means by which this object can be accessed by any thread that has not yet died, including possible actions by other objects or classes which are ready to be finalized, at which point the object may be discarded.
在一个对象的终结方法被调用以后,直到java虚拟机重新发现 包括其他准备好被终结的对象和类调用的对象之外 在任何尚未死亡的线程中不能以任何的路径访问到之前这个对象之前都不会有其他的操作。

The {@code finalize} method is never invoked more than once by a Java virtual machine for any given object.
所有对象的终结方法在java虚拟机调用过一次之后再也不会被调用。

Any exception thrown by the {@code finalize} method causes the finalization of this object to be halted, but is otherwise ignored.
所有终结方法抛出的异常都会导致这个对象的终结过程停止,但这个异常本身是会被忽视的。

噼里啪啦一阵BB,终于翻译完了。诶,每次翻译这种打断文字都是一种煎熬的过程。当然,这次也不例外。上面主要说了一下几点:

  1. finalize方法是被虚拟机垃圾回收的时候调用的,垃圾回收是回收那些“不可达”的对象;
  2. finalize方法位于Object类中,子类可以覆盖此方法来做一些自己想做的资源释放处理;
  3. finalize方法如果抛出异常,异常本身是会被忽略的,但是对象自身的终结过程会停止;
  4. 一个对象的finalize方法只会被调用一次。
    下面我就用一段代码来说明一下证实一下以上几点:
public class Main {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        A a = new A();
        System.gc();//强制触发垃圾回收
    }
}


class A{

    @Override
    protected void finalize()
    {
        System.out.println("执行终结方法");
    }
}

执行结果为:

由于A对象仍“可达”,所以垃圾回收不会回收该对象 自然不会触发其finalize方法。

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        A a = new A();
        System.gc();//强制触发垃圾回收
        a = null;
        System.gc();
    }

执行结果为:
执行终结方法

a引用置null后,A对象“不可达”,所以再次触发回收垃圾时,成功回收掉,并且触发finalize方法。

public class Main {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        A a = new A();
        a = null;
        System.gc();
    }
}
class A {
    @Override
    protected void finalize() throws Exception{
        throw new Exception();
    //  System.out.println("执行终结方法");
    }
}

执行结果为:

程序没有抛出异常。说明finalize方法抛出的异常确实是被忽略了。异常抛出后,该方法自然而然停止,即所谓的停止终结过程。

至于最后一点 finalize方法只会被调用一次,我会在深入理解java虚拟机的博客中举例。这里就先不写啦~

为什么避免使用终结方法

了解了什么是终结方法,我们来看看为什么说我们要避免使用终结方法。

1.终结方法不能保证会及时地执行
从一个对象不可达到它的终结方法被执行,这段时间是任意长的。这意味着注重时间的任务不应该放在finalize中。这里有两段时间:一个对象从可达到不可达,垃圾回收器何时开始回收垃圾是不确定的。除了显示进行垃圾回收,垃圾回收器只会在内存不够用的时候进行工作(这里面有一系列参数的设置,这里不谈)。这也就是意味着如果内存一直够用,系统很有可能永远不会调用终结方法。
结论:不应该依赖终结方法来更行重要的持久状态
**注:**System.gc()和System.runFinalization()不一定能保证终结方法一定会执行。唯一声称能够保证终结方法被执行的两个方法:Runtime.runFinalizersOnExit()和System.runFinalizersOnExit()由于有致命的缺点,所以已被废除。
2.终结方法有严重的性能损失
作者做过实验:创建和销毁对象的时间大约5.6ns而如果增加一个终结方法则时间增加到2400ns。性能损失可见一斑

使用显示终止方法代替终止方法来释放资源

如果一个类的资源确实需要释放,那么我们该怎么绕过终结方法呢?书中提供的方法是使用显示定义的终止方法,并且要求每个该类的每个实例在不再有用时调用该方法。此外该实例必须在自己的私有域记录下自己是否已经被终止,用来保证该实例被终止后不会再被调用。显示终止方法一般与try-finally联合使用。典型的终止方法是流的close方法。这个想必大家就会很有感触。每次用完流以后都必须使用close方法,来避免一些奇奇怪怪的错误。

终结方法的好处

1.充当“安全网”
在对象使用过程中如果忘记调用显示的终止方法,那么终结方法可用来当做最后的保障。(迟一点释放资源总比不释放好~)
2.处理对象的本地对等体”
本地对等体是一个本地对象(native object),普通对象通过本地方法(native method)委托给一个本地对象。本地对等体不是普通对象,垃圾回收器并不知道有这个东西的存在。当其java对等体被回收时,本地对等体并不会被回收。在本地对等体没有关键资源时,终结方法是用来回收最合适的工具。(当拥有关键资源时,就应该有一个显示终止方法)

注意“终结方法链”

所谓的终结方法链指的就是在子类覆盖父类的finalize方法后,子类的终结方法必须调用父类的终结方法,因为父类的终结方法是不会自己调用的。还有就是最好把调用父类的终结方法写在finally中 这样子就算子类的终结方法报错,能确保父类被终结。为了防止因自己粗心引起的父类未终结,我们可以建立一个“终结方法守卫者”——为每个将被终结的对象创建一个附加的对象。这样子的作用是把自身的终结方法写到外面的一个匿名类里面,不放在自己的类里面。该匿名类的单个实例就叫做“终结方法守卫者”。

、 public class Main {
public static void main(String[] args) {
// TODO Auto-generated method stub
B b = new B();
b = null;
System.gc();
}
}

class A {
    private final Object finalizerGuardian = new Object() {
        @Override
        protected void finalize() throws Exception {
            System.out.println("执行终结方法A1");
        }
    };

    @Override
    protected void finalize() throws Exception {
        System.out.println("执行终结方法A2");
    }
}
class B extends A{
    @Override
    protected void finalize() throws Exception {
        System.out.println("执行终结方法B");
    }
}

执行结果为:
执行终结方法A1
执行终结方法B

当类被初始化的时候,jvm会去装载其父类以及接口,并且初始化在父类里面的属性所以在实例化B对象时,jvm会加载A的类信息,由于在A中为finalizerGuardian 赋了值,所以也会初始化这个匿名内部类。
我们在B中没有调用A的终结方法但是A的守卫者帮我们终结了A。

总结

除非作为安全网或者是为了终止非关键的本地资源,我们应该尽可能的避免使用终结方法。我们如果使用了终结方法,我们就应该记得调用super.finalize()。或者考虑使用终结方法守卫者。

相关标签: java 终结方法