通常我们把分配出去后却无法回收的内存空间称为内存渗漏体(Memory Leaks)
以上这种程序设计的潜在危险性在Java这样以严谨安全着称的语言中是不允许的但是Java语言既不能限制程序员编写程序的自由性又不能把声明对象的部分去除(否则就不是面向对象的程序语言了)那么最好的解决办法就是从Java程序语言本身的特性入手于是Java技术提供了一个系统级的线程(Thread)即垃圾收集器线程(Garbage Collection Thread)来跟蹤每一块分配出去的内存空间当Java 虚拟机(Java Virtual Machine)处于空闲循环时垃圾收集器线程会自动检查每一快分配出去的内存空间然后自动回收每一快可以回收的无用的内存块
垃圾收集器线程是一种低优先级的线程在一个Java程序的生命周期中它只有在内存空闲的时候才有机会运行它有效地防止了内存渗漏体的出现并极大可能地节省了宝贵的内存资源但是通过Java虚拟机来执行垃圾收集器的方案可以是多种多样的
下面介绍垃圾收集器的特点和它的执行机制
垃圾收集器系统有自己的一套方案来判断哪个内存块是应该被回收的哪个是不符合要求暂不回收的垃圾收集器在一个Java程序中的执行是自动的不能强制执行即使程序员能明确地判断出有一块内存已经无用了是应该回收的程序员也不能强制垃圾收集器回收该内存块程序员唯一能做的就是通过调用System gc 方法来建议执行垃圾收集器但其是否可以执行什么时候执行却都是不可知的这也是垃圾收集器的最主要的缺点当然相对于它给程序员带来的巨大方便性而言这个缺点是瑕不掩瑜的
垃圾收集器的主要特点
.垃圾收集器的工作目标是回收已经无用的对象的内存空间从而避免内存渗漏体的产生节省内存资源避免程序代码的崩溃
.垃圾收集器判断一个对象的内存空间是否无用的标准是如果该对象不能再被程序中任何一个活动的部分所引用此时我们就说该对象的内存空间已经无用所谓活动的部分是指程序中某部分参与程序的调用正在执行过程中尚未执行完毕
.垃圾收集器线程虽然是作为低优先级的线程运行但在系统可用内存量过低的时候它可能会突发地执行来挽救内存资源当然其执行与否也是不可预知的
.垃圾收集器不可以被强制执行但程序员可以通过调用System gc方法来建议执行垃圾收集器
.不能保证一个无用的对象一定会被垃圾收集器收集也不能保证垃圾收集器在一段Java语言代码中一定会执行因此在程序执行过程中被分配出去的内存空间可能会一直保留到该程序执行完毕除非该空间被重新分配或被其他方法回收由此可见完全彻底地根绝内存渗漏体的产生也是不可能的但是请不要忘记Java的垃圾收集器毕竟使程序员从手工回收内存空间的繁重工作中解脱了出来设想一个程序员要用C或C++来编写一段万行语句的代码那么他一定会充分体会到Java的垃圾收集器的优点!
.同样没有办法预知在一组均符合垃圾收集器收集标准的对象中哪一个会被首先收集
.循环引用对象不会影响其被垃圾收集器收集
.可以通过将对象的引用变量(reference variables即句柄handles)初始化为null值来暗示垃圾收集器来收集该对象但此时如果该对象连接有事件监听器(典型的 AWT组件)那它还是不可以被收集所以在设一个引用变量为null值之前应注意该引用变量指向的对象是否被监听若有要首先除去监听器然后才可以赋空值
.每一个对象都有一个finalize( )方法这个方法是从Object类继承来的
.finalize( )方法用来回收内存以外的系统资源就像是文件处理器和网络连接器该方法的调用顺序和用来调用该方法的对象的创建顺序是无关的换句话说书写程序时该方法的顺序和方法的实际调用顺序是不相干的请注意这只是finalize( )方法的特点
.每个对象只能调用finalize( )方法一次如果在finalize( )方法执行时产生异常(exception)则该对象仍可以被垃圾收集器收集
.垃圾收集器跟蹤每一个对象收集那些不可到达的对象(即该对象没有被程序的任何活的部分所调用)回收其占有的内存空间但在进行垃圾收集的时候垃圾收集器会调用finalize( )方法通过让其他对象知道它的存在而使不可到达的对象再次复苏为可到达的对象既然每个对象只能调用一次finalize( )方法所以每个对象也只可能复苏一次
.finalize( )方法可以明确地被调用但它却不能进行垃圾收集
.finalize( )方法可以被重载(overload)但只有具备初始的finalize( )方法特点的方法才可以被垃圾收集器调用
.子类的finalize( )方法可以明确地调用父类的finalize( )方法作为该子类对象的最后一次适当的操作但Java编译器却不认为这是一次覆盖操作(overriding)所以也不会对其调用进行检查
.当finalize( )方法尚未被调用时System runFinalization( )方法可以用来调用finalize( )方法并实现相同的效果对无用对象进行垃圾收集
.当一个方法执行完毕其中的局部变量就会超出使用范围此时可以被当作垃圾收集但以后每当该方法再次被调用时其中的局部变量便会被重新创建
.Java语言使用了一种标记交换区的垃圾收集算法该算法会遍历程序中每一个对象的句柄为被引用的对象做标记然后回收尚未做标记的对象所谓遍历可以简单地理解为检查每一个
.Java语言允许程序员为任何方法添加finalize( )方法该方法会在垃圾收集器交换回收对象之前被调用但不要过分依赖该方法对系统资源进行回收和再利用因为该方法调用后的执行结果是不可预知的
通过以上对垃圾收集器特点的了解你应该可以明确垃圾收集器的作用和垃圾收集器判断一块内存空间是否无用的标准简单地说当你为一个对象赋值为null并且重新定向了该对象的引用者此时该对象就符合垃圾收集器的收集标准
判断一个对象是否符合垃圾收集器的收集标准这是SUN公司程序员认证考试中垃圾收集器部分的重要考点(可以说这是唯一的考点)所以考生在一段给定的代码中应该能够判断出哪个对象符合垃圾收集器收集的标准哪个不符合下面结合几种认证考试中可能出现的题型来具体讲解
Object obj = new Object ( ) ;
我们知道obj为Object的一个句柄当出现new关键字时就给新建的对象分配内存空间而obj的值就是新分配的内存空间的首地址即该对象的值(请特别注意对象的值和对象的内容是不同含义的两个概念对象的值就是指其内存块的首地址即对象的句柄而对象的内容则是其具体的内存块)此时如果有 obj = null 则obj指向的内存块此时就无用了因为下面再没有调用该变量了
请再看以下三种认证考试时可能出现的题型
程序段
.fobj = new Object ( ) ;
.fobj Method ( ) ;
.fobj = new Object ( ) ;
.fobj Method ( ) ;
问这段代码中fobj在第几行符合垃圾收集器的收集标准?
答第行因为第行的fobj被赋了新值产生了一个新的对象即换了一块新的内存空间也相当于为第行中的fobj赋了null值这种类型的题在认证考试中是最简单的
程序段
.Object sobj = new Object ( ) ;
.Object sobj = null ;
.Object sobj = new Object ( ) ;
.sobj = new Object ( ) ;
问这段代码中第几行的内存空间符合垃圾收集器的收集标准?
答第行和第行因为第行为sobj赋值为null所以在此第行的sobj符合垃圾收集器的收集标准而第行相当于为sobj赋值为null所以在此第行的sobj也符合垃圾收集器的收集标准
如果有一个对象的句柄a且你把a作为某个构造器的参数即 new Constructor ( a )的时候即使你给a赋值为nulla也不符合垃圾收集器的收集标准直到由上面构造器构造的新对象被赋空值时a才可以被垃圾收集器收集
程序段
.Object aobj = new Object ( ) ;
.Object bobj = new Object ( ) ;
.Object cobj = new Object ( ) ;
.aobj = bobj;
.aobj = cobj;
.cobj = null;
.aobj = null;
问这段代码中第几行的内存空间符合垃圾收集器的收集标准?
答第行注意这类题型是认证考试中可能遇到的最难题型了
行分别创建了Object类的三个对象aobjbobjcobj
行此时对象aobj的句柄指向bobj所以该行的执行不能使aobj符合垃圾收集器的收集标准
行此时对象aobj的句柄指向cobj所以该行的执行不能使aobj符合垃圾收集器的收集标准
行此时仍没有任何一个对象符合垃圾收集器的收集标准
行对象cobj符合了垃圾收集器的收集标准因为cobj的句柄指向单一的地址空间在第行的时候cobj已经被赋值为null但由cobj同时还指向了aobj(第行)所以此时cobj并不符合垃圾收集器的收集标准而在第行aobj所指向的地址空间也被赋予了空值null这就说明了由cobj所指向的地址空间已经被完全地赋予了空值所以此时cobj最终符合了垃圾收集器的收集标准 但对于aobj和bobj仍然无法判断其是否符合收集标准
总结
在Java语言中判断一块内存空间是否符合垃圾收集器收集标准的标准只有两个
.给对象