程序员都了解初始化的重要性但常常会忘记同样重要的清除工作毕竟谁需要清除一个int 呢?但在使用程序库时把一个对象用完后就弃之不顾的做法并非总是安全的当然Java有垃圾回收器来回收无用对象占据的内存资源但也有特殊情况假定你的对象(并非使用 new)获得了一块特殊的内存区域由于垃圾回收器只知道释放那些经由 new分配的内存所以它不知道该如何释放该对象的这块 特殊内存为了应对这种情况Java允许你在类中定义一个名为finalize( )的方法它的工作原理应该是这样的一旦垃圾回收器准备好释放对象占用的存储空间将首先调用其 finalize( )方法并且在下一次垃圾回收动作发生时才会真正回收对象占用的内存所以要是你打算用 finalize( )就能在垃圾回收时刻做一些重要的清除工作
这里有一个潜在的编程陷阱因为有些程序员(特别是 C++程序员)刚开始可能会误把finalize( )当作C++中的析构函数(C++中销毁对象必须用到这个函数)所以有必要明确区分一下在 C++中对象一定会被销毁(如果程序中没有错误的话)而 Java 里的对象却并非总是被垃圾回收的或者换句话说
. 对象可能不被回收
. 垃圾回收并不等于析构
牢记这些你就能远离困扰这意味着在你不再需要某个对象之前如果必须执行某些动作那么你得自己去做Java并未提供析构函数或相似的概念要做类似的清除工作你必须自己动手创建一个执行清除工作的普通方法例如假设某个对象在创建过程中会将自己绘制到屏幕上要是你不明确地从屏幕上将其擦除它可能永远得不到清除如果在
finalize( )里加入某种擦除功能当垃圾回收发生时(不能保证一定会发生)finalize( )得到了调用图像就会被擦除要是垃圾回收没有发生图像就会一直保留下来
也许你会发现只要程序没有濒临存储空间用完的那一刻对象占用的空间就总也得不到释放如果程序执行结束并且垃圾回收器一直都没有释放你创建的任何对象的存储空间则随着程序的退出那些资源会全部交还给操作系统这个策略是恰当的因为垃圾回收本身也有开销要是不使用它那就不用支付这部分开销了
finalize( )用途何在?
此时你已经明白了不该将finalize( )作为通用的清除方法那么finalize( )的真正用途是什么呢?
这引出了要记住的第三点
.垃圾回收只与内存有关
也就是说垃圾回收器存在的唯一原因是为了回收程序不再使用的内存所以对于与垃圾回收有关的任何行为来说(尤其是finalize( )方法)它们也必须同内存及其回收有关
但这是否意味着要是对象中含有其他对象finalize( )就应该明确释放那些对象呢?不——无论对象是如何创建的垃圾回收器都会负责释放对象占据的所有内存这就将对 finalize( )的需求限制到特殊情况之下你通过某种非创建对象的方式为对象分配了存储空间不过你也看到了Java中一切皆为对象那这种特殊情况是怎么回事呢?
看来之所以要有finalize( )是由于你可能在分配内存时采用了类似C语言中的做法而非Java中的通常做法这种情况主要发生在使用本地方法的情况下它是在Java中调用非Java代码的一种方式本地方法目前只支持C和C++但它们可以调用其它语言写的代码所以你实际上可以调用任何代码在非Java代码中也许会调用类似C的malloc( )函数用它分配存储空间而且除非调用了free( )函数否则存储空间将不会得到释放从而造成内存洩露当然free( )是C和C++中的函数所以你需要在finalize( )中用本地方法调用它
至此你或许已经明白了不要过多地使用finalize( )的道理了对它确实不是进行普通的清除工作的合适场所那么普通的清除工作应该在哪执行呢?
你必须执行清除
为清除一个对象用户必须在进行清除的时刻调用执行清除动作的方法听起来似乎很简单但却与C++中的析构函数的概念稍有抵触在C++中所有对象都会被销毁或者说 应该被销毁如果在C++中创建了一个局部对象(就是在堆栈上创建Java中可不行)此时的销毁动作发生在以右花括号为边界的此对象作用域的末尾处进行如果对象是
用new创建的(类似于Java)那么当程序员调用C++的delete( )时(Java没有这个命令)就会调用相应的析构函数如果程序员忘了那么永远不会调用析构函数就会出现内存洩露对象的其他部分也不会得到清除这种错误很难跟蹤这也是让 C++程序员转向 Java的一个主要因素
相反Java不允许创建局部对象你必须使用new在Java中也没有delete来释放对象因为垃圾回收器会帮助你释放存储空间甚至可以肤浅地认为正是由于垃圾收集机制的存在使得 Java 没有析构函数然而随着学习的深入你就会明白垃圾回收器的存在并不能完全代替析构函数(而且你绝对不能直接调用finalize( )所以这也不是一个恰当的
途径)如果你希望进行除释放存储空间之外的清除工作你还是得明确调用某个恰当的Java方法这就等同于使用析构函数了而且没有它方便
记住无论是垃圾回收还是终结都不保证一定会发生如果Java虚拟机(JVM)并未面临内存耗尽的情形它是不会浪费时间在回收垃圾以恢复内存上的
终结条件
通常你不能指望finalize( )你必须创建其它的清除方法并且明确地调用它们看来finalize( )只能存在于程序员很难用到的一些晦涩用法里了不过finalize( )还有一个有趣的用法它并不依赖于每次都要对finalize( )进行调用这就是对象终结条件的验证
当你对某个对象不再感兴趣也就是它可以被清除时这个对象应该处于某种状态使它占用的内存可以被安全地释放例如要是对象代表了一个打开的文件在对象被回收前程序员应该关闭这个文件只要对象中存在没有被适当清除的部分你的程序就存在很隐晦的错误finalize( )的价值在于可以用来最终发现这种情况尽管它并不总是会被调用如果某次
finalize( )的动作使得bug被发现那你就可据此找出问题所在——这才是你真正关心的
以下是个简单的例子示范了可能的使用方式
class Book {
boolean checkedOut = false;
Book(boolean checkOut) {
checkedOut = checkOut;
}
void checkIn() {
checkedOut = false;
}
public void finalize() {
if(checkedOut)
Systemoutprintln(Error: checked out);
// Normally youll also do this:
// superfinalized();
}
}
public class TerminationCondition {
public static void main(String[] args) {
Book novel = new Book(true);
// Proper cleanup:
novelcheckIn();
// Drop the reference forget to clean up:
new Book(true);
// Force garbage collection & finalization:
Systemgc();
}
}
本例的终结条件是所有的Book对象在被当作垃圾回收前都应该被签入(check in)但在main( )方法中由于程序员的错误有一本书未被签入要是没有 finalize( )来验证终结条件将很难发现这种错误
注意Systemgc( )用于强制终结动作的进行即使不这么做的话通过重复的执行程序(假设程序将分配大量的存储空间而导致垃圾回收动作的执行)最终也能找出错误的Book对象