请丢弃 finalize

用过 JDK9 的同学应该发现了,finalize 方法在 JDK9 中已经被标记为 deprecated,今天探讨一下 finalize 方法。如果没有特别的原因,不要实现 finalize 方法,也不要指望利用它来进行资源回收。因为你无法保证 finalize 什么时候执行,执行的是否符合预期。使用不当会影响性能,导致程序死锁、挂起等。

首先要明白的是为什么会出现 finalize 方法,因为早期一部分程序员是写 C++ 的,仍然保留析构函数释放资源的行为,所以为了让他们平滑的过渡到 Java 并且适应 Java,所以出现了 finalize 方法,用来对象被回收前的一次自我拯救,完成释放资源的操作。那么 Object 的 finalize () 方法的作用是否与 C++ 的析构函数作用相同呢?其实 finalize 方法与 C++ 的析构函数是有很大不同的,析构函数调用确定,而 finalize 是不确定的。重写了 finalize 的对象如果未被引用就会被放置于 F-Queue 队列,而且由一个优先级极底的线程成来执行 finalize 方法,而且方法执行随时可能会被终止。

finalize 的执行是和垃圾收集关联在一起的,一实现了非空的 finalize 方法,就会导致相应对象回收呈现数量级上的变慢,有人专做过 benchmark,大概是 40~ 50 倍的下降。因为,finalize 被设计成在对象被垃圾收集前调用,这就意味着实现了 finalize 方法的对象是个 “特殊公民”, JVM 要对它进行额外处理。finalize 本质上成为了快速回收的阻碍者,可能导致你的对象经过多个垃圾收集周期才能被回收。

下面来看一个示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Finalization {
public static Finalization finalization;
@Override
protected void finalize(){
System.out.println ("Finalized");
finalization = this;
}

public static void main(String [] args) {
Finalization f = new Finalization();
System.out.println ("First print: " + f);
f = null;
System.gc ();
try {
// 休息一段时间,让上面的垃圾回收线程执行完成
TimeUnit.SECONDS.sleep (1);
} catch (InterruptedException e){
e.printStackTrace ();
}
System.out.println ("Second print: " + f);
System.out.println (f.finalization);
}
}

mark

可见 finalize 会拖慢垃圾收集,导致对象堆积,容易导致 OOM,而且使用 finalize 释放资源是不合理的,资源应该是用完立即释放的,或者采用资源池来解决这个问题,而不是依靠被动垃圾回收时触发 finalize 方法。