Thinking In Java笔记(第五章 初始化与清理(二))

第五章 初始化与清理(二)5.5 清理:终结处理和垃圾回收

清理的工作常常被忽略,Java有垃圾回收器负责回收无用对象占据的内存资源。但也有特殊情况:假定对象(并非使用new)获得了一块”特殊”的内存区域,由于垃圾回收器只知道释放那些由new分配的内存,所以不知道如何释放特殊内存。Java允许在类中定义一个名为finalize()的方法,工作原理”假定”是这样的:一旦垃圾回收器准备好释放对象占用的存储空间,首先调用其finalize()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。

Java的finalize()和C++的析构函数有所不同,C++中对象一定会被销毁(如果程序中没有缺陷的话),而Java中对象并非总是被垃圾回收。即:1.对象可能不被垃圾回收。2.垃圾回收并不等于”析构”

Java并未提供”析构函数”或类似的概念,要做的类似的清理工作,必须自己动手创建一个执行清理工作的普通方法。当”垃圾回收”发生时(不能保证一定会发生),finalize()得到了调用,相应的工作就会进行,如果垃圾回收没有发生,就不会被调用。

只要程序没有濒临存储空间用完的那一刻,对象占用的控件就总也得不到释放,如果程序执行结束,并且垃圾回收器一直都没有释放你创建的任何对象的存储空间,随着程序的退出,资源会全部还给操作系统。这个策略是恰当的,因为垃圾回收本身也占用内存控件,如果不使用,内存开销会变小。

5.5.1 finalize()用途何在

垃圾回收有关的任何行为(尤其是finalize()方法),,它们也必须同内存及回收有关。finalize()方法存在的意义是为了回收那些用new创建出来的对象之外的特殊存储空间,是由于在分配内存时,可能采用了类似C语言中的做法,而非Java中的通常做法(new)。这种情况主要发生在使用”本地方法”的情况下,本地方法是一种在Java中调用非Java代码的方式。本地方法目前只支持C和C++,但它们可以调用其他语言写的代码。

在非Java代码中,也许会调用C的malloc()函数系列来分配存储空间,而且除非调用free()函数,否则存储空间将得不到释放,从而造成内存泄漏。free()是C和C++中的函数,所以需要在finalize()中用本地方法调用它。

5.5.2 你必须实施清理

要清理一个对象,用户必须在需要清理的时刻调用执行清理动作的方法。在C++中,所有对象都会被销毁,都应该被销毁。如果在C++中创建了一个局部对象(也就是在堆栈上创建,这在Java中行不通),此时的销毁动作发生在以”右花括号”为边界的、此对象作用域的末尾处。如果对象是用new创建的,那么当程序员调用C++的delete操作符(Java没有这个命令),就会调用相应的析构函数。如果没有调用delete,那永远不会调用析构函数,这样会出现内存泄漏。

Java中不允许创建局部对象,必须使用new创建对象。在Java中,也没有释放对象的delete,垃圾回收器会帮你释放存储空间。

5.5.3 终结条件

通常不能指望finalize(),必须创建其他的”清理”方法,并明确地调用它们。finalize()还有一个又去的用法,并不依赖于每次都要对finalize进行调用,也就是对象终结条件的验证。

当对某个对象不再感兴趣–也就是它可以被清理了,这个对象应该处于某种状态,使它占用的内存可以被安全地释放。下面的例子示范了finalize()可能的使用方法:

class Book {boolean checkedOut = false;Book(boolean checkOut) {checkedOut = checkOut;}void checkIn() {checkedOut = false;}protected void finalize() {if(checkedOut)System.out.println(“Error : checked out”);}}public class Test {public static void main(String[] args) {Book novel = new Book(true);novel.checkIn();new Book(true);System.gc();}}

本例的终结条件时:所有Book对象在被当作垃圾回收前都应该被checkIn(),但是在new Book(true)这个对象没有被checkIn,要是没有finalize()来验证终结条件,很难发现这种缺陷。

System.gc()用于强制进行和终结动作。

5.5.4 垃圾回收器如何工作

垃圾回收器对于提高对象创建速度有明显的效果,Java虚拟机在工作的时候,存储空间的释放会影响存储空间的分配,由于垃圾回收器的存在,Java从堆分配空间的速度可以和其他语言从堆栈上分配控件的速度相媲美。

先了解其他系统中的垃圾回收机制将能帮助我们更好的理解Java中的回收机制,引用记数是一种简单但速度很慢的垃圾回收技术。每个对象都含有一个引用计数器,当有引用和对象连接的时候,引用记数加1,当引用离开作用域或被置为null时,引用记数减1。

垃圾回收器会在含有全部对象的列表上遍历,当发现某个对象的引用记数为0时,就释放其占用的空间(但是,引用技术模式经常会在计数值变为0的时候立即释放对象)。如果对象之间存在循环饮用,可能会出现”对象应该被回收,但引用记数却不为0”的情况。引用记数常用来说明垃圾收集的工作方式,但似乎从来未被应用与任何一种Java虚拟机实现中。

在更快的一些模式中,垃圾回收器并非基于引用记数技术,而是:对任何“活”的对象,一定能最终追溯到其存活在堆栈或静态存储区中的引用。如果从堆栈和静态存储区开始,遍历所有的引用,就能找到所有”活”的对象,对于每个引用,必须追踪和它关联的对象,然后是此关联对象的所有引用,反复进行,直到全部被访问。

在上述的方式下,Java虚拟机将采用一种自适应的垃圾回收技术。其中有一种找到存活对象的方法名为停止-复制(stop-and-copy)。显然这意味着先暂停程序的运行(不属于后台回收模式),然后将所有存活的对象从当前堆复制到另外一个堆,没有被复制的都是应当被回收的。

当把对象从一处搬到另外一处时,所有之乡它的那些引用都必须修正。位于堆或静态存储区的引用可以直接被修正,但可能还有其他指向这些对象的引用,它们在遍历的过程中才能被找到。对于这种复制式回收器而言,效率会降低。1.需要两个堆来回倒腾,某些Java虚拟机对此问题的处理方式是,按需从堆中分配几块较大的内存,复制动作发生在这些大块内存之间。2.程序进入稳定状态后,产生少量垃圾,但是复制式回收器还是会不停的复制,对于第二种情况,一些Java虚拟机会进行检查:要是没有新的垃圾产生,就会切换到另一种工作模式(标记-清扫mark-and-sweep),Sun公司早期版本的Java虚拟机使用了这种技术。对一般用途而言,”标记-清扫”方式速度相当慢,但是当只会产生少量垃圾甚至不会产生垃圾的时候,速度就很快了。

天下爱情,大抵如斯。

Thinking In Java笔记(第五章 初始化与清理(二))

相关文章:

你感兴趣的文章:

标签云: