避免使用终结方法和清除方法
终结方法(Object.finalize()) 通常是不可预测的,也是很危险的,一般情况下是不必要的。使用终结方法会导致行为不稳定、性能降低,以及可移植性问题。根据经验,应该避免使用终结方法。 在 Java9 中用清除方法Cleaner代替了终结方法。Cleaner虽然没有finalize()那么危险,但仍然是不可预测、运行缓慢,一般情况下也是不必要的。
在 Java 中,当一个对象变得不可到达的时候,垃圾回收器会回收与该对象相关联的存储空间,并不需要我们做专门的工作。而对于回收其他的非内存资源
- 什么叫对象不可达?这个要涉及对死亡对象的判断方法。堆中几乎放着所有的对象实例,对堆回收前的第一步就要判断哪些对象已经死亡(即不能再被任何途径使用的对象)
- 可达性分析算法(Java中用来判断对象是否可达的算法):这个算法的基本思想就是在堆中选取一些名为“GC Roots”的对象作为起点,从这些节点向下搜索,这些节点引用着的对象说明是科大的。当一个对象到 GC Roots 没有任何引用链相连,证明该对象不可用,需要被回收
- 详细内容请翻阅:可达性分析算法
缺点
终结方法和清除方法的缺点在于不能保证会被及时执行。 可以尝试一下案例代码
public class FinalizeExample {
public static void main(String[] args) throws IOException {
System.out.println("正常");
FinalizeExample example1 = new FinalizeExample();
example1 = null;
// 注释掉这一行代码finalize()就不会执行了
System.gc();
}
@Override
protected void finalize() throws Throwable {
System.out.println("----------finalize执行------------");
super.finalize();
}
}
在 Java 中,由于 GC 的自动回收机制,因而并不能保证finalize方法会被及时执行(垃圾对象的回收时机具有不确定性)。并且 Java 语言规范不仅不保证终结方法或者清除方法会被及时执行,且根本就不保证它们会被执行。
虽然上面案例中System.gc()可以使finalize()执行,但它并不保证终结方法或清除方法一定会被执行。能够做到这一功能的方法是System.runFinalizersOnExit和Runtime.runFinalizersOnExit。这两个方法都有致命缺点,并且被废弃好久了
书中所说使用终结方法和清除方法有一个非常严重的性能损失,从实用角度,已经用很大篇幅去讲不要用终结方法和清除方法,本人也就没有进行具体测试了
安全问题
终结方法有一个严重的安全问题:终结方法攻击
此处具体案例借鉴:Finalizer attack
容易遭受攻击的案例
class Vulnerable {
String password;
Vulnerable(String password) {
if (!checkVerification(password)) {
throw new IllegalArgumentException("Fail to verification");
}
this.password = password;
}
private static boolean checkVerification(String password) {
return "123".equals(password);
}
}
- 攻击类
public class Attack extends Vulnerable{
public static Attack instance;
Attack(String password){
super(password);
}
public void finalize(){
instance = this;
}
public static void main(String[] args){
try{
new Attack("23");
}catch(Exception e){
e.printStackTrace();
}
System.gc();
System.runFinalization();
if(instance != null){
System.out.println("instance is created!");
}
}
}
最终输出
java.lang.IllegalArgumentException: Fail to verification
instance is created!
从构造器抛出的异常,应该足以防止对象继续存在,但有了终结方法的存在,使对象又复活了
而final类不会受到终结方法攻击,因为不会有子类
为防止非final类受到终结方法攻击,要编写一个空的final的finalize方法
如果类的对象中封装的资源确实需要终止,只需要让类实现AutoCloseable,并要求客户端在每个实例不需要的时候调用close()方法,通常使用try-with-resources
好处
终结方法和清除方法的好处有两个
- 当资源的所有者忘记调用
close()方法,终结方法或清除方法可以充当“安全网”,虽然并不能保证这些方法会被及时运行,但迟一点释放资源总比永远不释放要好