“5.5 清理:终结处理和垃圾回收”,在了解了初始化的重要性以后,就要了解清理的重要性了。在使用程序库的时候,把一个对象使用完以后就“弃置不顾”的做法并非总是安全的。一般情况下,Java的垃圾回收器负责回收无用对象占用的内存。但是对于那些不是经过new出来的对象,垃圾回收器不知道如何释放这块特殊的内存。Java提供了finalize方法,它的工作原理是这样的:一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用其finalize方法,并且在下次垃圾回收动作发生时,才会真正回收对象占用的内存。
对于这一点,经常被做C++的工程师吐槽。有些C++转到Java的工程师在一开始做Java开发的时候,往往把finalize方法错认为是析构函数的作用,认为资源肯定会被立马释放。其实这里面需要关注两个困扰:
1、对象可能不被垃圾回收,就算被回收,可能不会立刻发生
2、垃圾回收并不等于析构
牢记上面两点,即可远离困扰。意思是说我们不能依赖finalize函数做一些资源释放的操作,你可以理解为finalize函数是一种可能发生可能不发生的函数。所以对于一些重要的资源内存区域,甚至所有的资源内存区域,我们必须自己收到创建一个执行清理工作的方法。
也许你会发现,只要程序没有濒临存储空间用完的那一刻,对象占用的空间就总也得不到释放。如果程序执行结束,并且垃圾回收器一直都没有释放你创建的任何对象的存储空间,则随着程序的退出,那些资源也会全部交还操作系统。这个策略是恰当的,因为垃圾回收本身也有开销,要是不使用它,那就不用支付这部分开销了。
那么finalize的用途何在呢?
请记住:垃圾回收只与内存有关。也就是说,使用垃圾回收器的唯一原因是为了回收程序不再使用的内存。这里的程序不再使用的内存是指通过Java语言层面传统的方式new出来或者是创建出来的对象占用的内存,垃圾回收器会负责回收。如何回收以及回收的时间点,回收多少都是垃圾回收器内部机制决定的,有专门的书籍或者篇章会去讲垃圾回收器。那什么样的内存不是垃圾回收器负责回收的呢,举个例子,Java中调用本地方法,比如c/c++写的方法,它产生的内存,垃圾回收器就不会收。那么垃圾回收器负责回收的内存区域叫什么呢,在JVM内存模型中叫堆,这个堆以外的就是他不负责回收了。
虽然finalize并不是总是被调用,但是一旦被finalize被调用了,而我们在finalize方法中实现了检查对象中存在没有被适当清理的部分,那么我们就可以借助这种方式找到程序中问题的缺陷,这才是我们关注的。比如下面的例子:
classBook { booleancheckedOut=false; Book(booleancheckOut) { checkedOut=checkOut; } voidcheckIn() { checkedOut=false; } protectedvoidfinalize() { if(checkedOut) System.out.println("Error: checked out"); // Normally, you’ll also do this: // super.finalize(); // Call the base-class version } } } publicclassTerminationCondition { publicstaticvoidmain(String[] args) { Booknovel=newBook(true); // Proper cleanup: novel.checkIn(); // Drop the reference, forget to clean up: newBook(true); // Force garbage collection & finalization: System.gc(); } }
/* Output:
Error: checked out
*///:~
本例子的终结条件是:所有的Book对象再被当作垃圾回收前都应该被签入。但是在main方法中,由于程序员的错误,有一本书未被签入,要是没有finalize方法来验证终结条件,将很难发现这种缺陷。最后还是建议大家,尽量少用finalize吧。