本文是 《Effective Java Second Edition》第7条的读书笔记。
以下使用的JVM参数为-Xmx5m -Xmn5m
。
首先我们要弄清楚什么是终结方法(finalizer
)?
终结方法是我们Object
类中的一个通用方法,其定义如下
@Deprecated(since="9")
protected void finalize() throws Throwable { }
上面的注解表示其在JDK1.9
中是已经不再支持使用的了。
对于学习过C++
的都应该会知道有一个与构造函数相对应的函数我们称之为析构函数。而我们的终结方法就是类似于C++
中的这个析构函数,只要是用来回收一些不可达的对象的。
但是这个终结方法有一个致命的缺点就是:
当一个对象变得不可达的时候,该对象的终结方法的执行时间是任意的,在大多数的情况下可能是不会执行的。
我们在这里来测试一下对象的终结方法时候会被执行。
如下代码
package com.blog.effective.note7;
/**
* 自定义一个重写终结方法的方法.
*
* @author 张俊强~.
* @date 2017/11/27-22:17.
*/
public class MyFileOutputStream {
public MyFileOutputStream() {
super();
}
@Deprecated(since="9")
protected void finalize() throws Throwable {
System.out.println("调用终结方法");
System.exit(0); //调用终结方法我们强制退出
super.finalize();
}
}
在客户端我们的测试代码如下
public static void main(String[] args) {
int i = 0; //标记while循环执行的次数
while (true) {
MyFileOutputStream myFileOutputStream = new MyFileOutputStream();
System.out.println("M:" + (i++));
}
}
测试后的结果为:在while循序执行了6769次后,开始调用了对象的终结方法。
[注]. 如果不使用while()
循环(只执行一次),在我测试很多次之后还是没有发现有调用终结方法的现象。
有两个方法方法会加快对象的终结方法的调用速度。
System.gc()
和 System.runFinalization()
如下的客户端代码
public static void main(String[] args) {
int i = 0; //标记while循环执行的次数
while (true) {
MyFileOutputStream myFileOutputStream = new MyFileOutputStream();
System.gc();
System.out.println("M:" + (i++));
}
}
输入如下
M:0
M:1
调用终结方法
public static void main(String[] args) {
int i = 0; //标记while循环执行的次数
while (true) {
MyFileOutputStream myFileOutputStream = new MyFileOutputStream();
System.runFinalization();
System.out.println("M:" + (i++));
}
}
输入如下
M:2038
M:2039
M:2040
调用终结方法
可以原来需要运行六千多次才会调用终结方法,被提前调用了。
那么有什么会保证相应的终结方法一定会被执行呢?
System.runFinalizersOnExit()
和 Runtime.runFinalizersOnExit()
可以保证finalizer()
方法一定执行,但是这两个方法已经废弃。
如果不建议使用终结方法,那么有什么替代做法呢?
使用try-finally结构来显示的调用父类的终结方法
这就是我们在一些对于IO
流的操作,或者数据库的连接的时候,会要求在finally
中显示的调用close()
方法的原因。
我们那FileInputStream
这个类为例,查看一下其finalizer()
方法。
@Deprecated(since="9")
protected void finalize() throws IOException {
if ((fd != null) && (fd != FileDescriptor.in)) {
close();
}
}
我们就可以知道其对应的终结方法也是调用close()
方法来释放空间的。
使用finalizer guardian
(终结方法守卫者)
为了防止之类在重写父的终结方法时候忘记加上super.finalize();
,我们可以就使用这个方法。
如下代码
public class MyFileOutputStream {
public MyFileOutputStream() {
super();
}
private final Object finalizerGuardian =new Object(){
@Override
protected void finalize() throws Throwable {
System.out.println("调用终结方法");
super.finalize();
}
};
}
客户端程序
public static void main(String[] args) {
int i = 0; //标记while循环执行的次数
MyFileOutputStream myFileOutputStream = new MyFileOutputStream();
myFileOutputStream=null;
System.gc();
}
发现我们的终结方法被调用了。