Java定义了finalize()方法,用于在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。在《Java编程思想》一书中,有这样一段话:
Java有垃圾回收器负责回收无用对象占据的内存资源。但也有特殊情况:假定你的对象(并非使用new)获得了一块“特殊”的内存区域,由于垃圾回收器只知道释放那些经由new分配的内存,所有它不知道如何释放该对象的这块“特殊”内存。为了应对这种情况,Java允许在类中定义一个名为finalize()的方法。它的工作原理“假定”是这样的:一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用其finalize()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。所以要是你打算用finalize(),就能在垃圾回收时刻做一些重要的清理工作。
- 和析构函数的区别
finalize()方法与C++中的析构函数有几分相似之处,但是二者又不完全等同。在C++中,既有基于栈区的局部对象,又有基于堆区的全局对象,其中栈区对象的内存由编译器自动分配和释放,而堆区对象的内存则需要由程序员来分配和释放;而在Java中,由于所有对象都在堆内存区,废弃对象所占用的内存都由Java中的垃圾回收器来回收,Java的设计者认为不需要析构函数的存在,这关键在于Java的“不需要人工参与”的垃圾回收机制:
当你不再需要使用一个对象,或者说,当不存在引用指向某一个对象时,Java虚拟机(JVM)会将该对象标记为释放状态。但是,这并不意味着JVM一定会释放这些对象所占用的内存,或许只有到程序濒临存储空间被用完的那一刻,对象占用的空间才会得到释放,即使你调用System.gc()方法也不见得一定有效,因为这只是给JVM一个建议,而非命令。
这便造成了:在C++中,当要销毁某个对象时,必须调用该对象的析构函数,这是可预期的;而在Java中,对象却并非总是被垃圾回收,这是不可预期的。
- finalize()的用途
垃圾回收器虽然可以自动回收不再被使用的对象的内存,但是它却也仅限于能够释放通过使用new而获得的存储区域。换句话说,若要释放通过某种创建对象方式以外的方式为对象分配的存储空间,这便是finalize()的用处所在。这主要发生在使用“本地方法”的情况下,本地方法是一种在Java中调用非Java代码的方式。在非Java代码中,也许会调用C的malloc()函数系列来分配存储空间,而且除非调用了free()函数,否则存储空间将得不到释放,从而造成内存泄漏。当然,free()是C和C++中的函数,所以需要在finalize()中用本地方法调用它。但是,你不可能知道何时——甚至是否——finalize()被调用,因此你不应该依赖于使用finalize方法来回收某些短缺的资源,而必须创建其他的“清理”方法,并且明确地调用它们。
不过,finalize()还有一个并不依赖于每次都要被调用的用法,那就是对象终结条件的验证。通俗地讲就是:某个对象要么不被销毁,一旦要被销毁,就应该满足某种终结条件。finali()可以用来检查要被销毁的对象是否满足这种条件。我们通过下面这个例子来进行说明:
class Book {
boolean checkedOut = false;
Book(boolean checkOut) {
checkedOut = checkOut;
}
void checkIn() {
checkedOut = false;
}
public void finalize() {
if(checkedOut)
System.out.println("Error: checked out");
// Normally, you'll also do this:
// super.finalized();
}
}
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();
}
}
在该例中,任何Book对象在被销毁之前,都应该被被签入(check in),这便是终结条件。如果由于程序员的失误导致某本书未被签入就使得该对象被销毁的话,finalize()就会发现这一缺陷。