finalize()的设计理念
众所周知,Java 中有垃圾回收器回收无用对象占用的内存,但是,假若,你创建的对象不是通过 new 来分配内存的,而垃圾回收器只知道如何释放用 new 创建的对象的内存,所以它不知道如何回收不是 new 分配的内存。为了处理这种情况,Java 允许在类中定义一个名为 finalize()
的方法。
finalize()的工作原理
它的工作原理"假定"是这样的:当垃圾回收器准备回收对象的内存时,首先会调用其 finalize()
方法,并在下一轮的垃圾回收动作发生时,才会真正回收对象占用的内存。所以如果你打算使用 finalize()
,就能在垃圾回收时做一些重要的清理工作。finalize()
是一个潜在的编程陷阱,因为一些程序员(尤其是 C++ 程序员)会一开始把它误认为是 C++ 中的析构函数(C++ 在销毁对象时会调用这个函数)。所以有必要明确区分一下:在 C++ 中,对象总是被销毁的(在一个 bug-free 的程序中),而在 Java 中,对象并非总是被垃圾回收,或者换句话说:
- 对象可能不被垃圾回收。
- 垃圾回收不等同于析构。
Java中的finalize() 和C++ 中的析构函数的区别
Java 没有析构器或类似的概念,所以你必须得自己创建一个普通的方法完成这项清理工作。例如,对象在创建的过程中会将自己绘制到屏幕上。如果不是明确地从屏幕上将其擦除,它可能永远得不到清理。如果在 finalize()
方法中加入某种擦除功能,那么当垃圾回收发生时,finalize()
方法被调用(不保证一定会发生),图像就会被擦除,要是"垃圾回收"没有发生,图像则仍会保留下来。
也许你会发现,只要程序没有濒临内存用完的那一刻,对象占用的空间就总也得不到释放。如果程序执行结束,而垃圾回收器一直没有释放你创建的任何对象的内存,则当程序退出时,那些资源会全部交还给操作系统。这个策略是恰当的,因为垃圾回收本身也有开销,要是不使用它,那就不用支付这部分开销了。
finalize()
的用途
看起来之所以有 finalize()
方法,是因为在分配内存时可能采用了类似 C 语言中的做法,而非 Java 中的通常做法。这种情况主要发生在使用"本地方法"的情况下,本地方法是一种用 Java 语言调用非 Java 语言代码的形式(关于本地方法的讨论,见本书电子版第2版的附录B)。本地方法目前只支持 C 和 C++,但是它们可以调用其他语言写的代码,所以实际上可以调用任何代码。在非 Java 代码中,也许会调用 C 的 malloc()
函数系列来分配存储空间,而且除非调用 free()
函数,不然存储空间永远得不到释放,造成内存泄露。但是,free()
是 C 和 C++ 中的函数,所以你需要在 finalize()
方法里用本地方法调用它。
finalize()
还有一个有趣的用法——终结条件验证
通常,不能指望 finalize()
,你必须创建其他的"清理"方法,并明确地调用它们。所以看起来,finalize()
只对大部分程序员很难用到的一些晦涩内存清理里有用了。但是,finalize()
还有一个有趣的用法,它不依赖于每次都要对 finalize()
进行调用,这就是对象终结条件的验证。
当对某个对象不感兴趣时——也就是它将被清理了,这个对象应该处于某种状态,这种状态下它占用的内存可以被安全地释放掉。例如,如果对象代表了一个打开的文件,在对象被垃圾回收之前程序员应该关闭这个文件。只要对象中存在没有被适当清理的部分,程序就存在很隐晦的 bug。finalize()
可以用来最终发现这个情况,尽管它并不总是被调用。如果某次 finalize()
的动作使得 bug 被发现,那么就可以据此找出问题所在——这才是人们真正关心的。以下是个简单的例子,示范了 finalize()
的可能使用方式:
// housekeeping/TerminationCondition.java
// Using finalize() to detect a object that
// hasn't been properly cleaned up
import onjava.*;
class Book {
boolean checkedOut = false;
Book(boolean checkOut) {
checkedOut = checkOut;
}
void checkIn() {
checkedOut = false;
}
@Override
protected void finalize() throws Throwable {
if (checkedOut) {
System.out.println("Error: checked out");
}
// Normally, you'll also do this:
// super.finalize(); // Call the base-class version
}
}
public class TerminationCondition {
public static void main(String[] args) {
Book novel = new Book(true);
// Proper cleanup:
novel.checkIn();
// Drop the reference, forget to clean up:
new Book(true);
// Force garbage collection & finalization:
System.gc();
new Nap(1); // One second delay
}
}
输出:
Error: checked out
本例的终结条件是:所有的 Book 对象在被垃圾回收之前必须被登记。但在 main()
方法中,有一本书没有登记。要是没有 finalize()
方法来验证终结条件,将会很难发现这个 bug。
注意,
System.gc()
用于强制进行终结动作。但是即使不这么做,只要重复地执行程序(假设程序将分配大量的存储空间而导致垃圾回收动作的执行),最终也能找出错误的 Book 对象