当前位置:首页>>开发编程>>JAVA>>新闻内容  
怎样处理Java最终化的内存保留问题
作者: 发布时间:2006-3-14 17:28:30 | 【字体:
    最终化允许实现Java对象的最后清理;但是,就算不显式地使用它,它也能延迟资源的回收。在本文中你还会学习如何避免类似的内存保留问题。

  一、 引言

  最终化是Java编程语言的一个特性:它允许你对垃圾收集器发现的不可达的对象进行最后清理。典型地用于回收与一对象相关联的本地资源。下面是一个简单的最终化示例:

public class Image1 {
 //指向本地图像数据
 private int nativeImg;
 private Point pos;
 private Dimension dim;
 //它释放本地图像;
 //随后对它的调用将被忽略
 private native void disposeNative();
 public void dispose() { disposeNative(); }
 protected void finalize() { dispose(); }
 static private Image1 randomImg;
}


图1.一个可最终化的对象

  有时,在一个Image1实例变为不可达的时,Java虚拟机(JVM)将调用它的finalize()方法来确保含有图像数据(在本例中被整数nativeImg所指向)的本地资源已经被回收。然而,请注意,该finalize()方法,先不管它被JVM进行专门对待,是一个任意的方法-它包含任意的代码。特别地,它能存取任何对象中的任何字段(在本例中指pos和dim)。令人惊讶的是,它还能使该对象再次成为可达的-比如说通过让它从一个静态字段成为可达的(如,randomImg=this;)。我根本不推荐使用后面这种编程实践,但是遗憾的是,Java编程语言允许它。

  下面步骤描述一个可最终化的对象obj的生命周期-也即,这是一个其类中有一个非平凡的(non-trivial)终结器的对象(见图1):


图2.垃圾收集器确定这个obj是不可达的


  1. 当obj被分配时,JVM内部地记录下这个obj是可最终化的(这在典型情况下会减慢现代JVM具有的其它方面的分配路径)。

  2. 当垃圾收集器确定该obj是不可达的时,它注意到,这个obj是可最终化的(因为它在分配时就被记录下来)并且把它添加到JVM的最终化队列上。它还确保从obj可达的所有对象被保留起来,即使它们从其它对象也许是不可达的,因为它们可能会被终结器所存取。图2展示了Image1的一个实例的情况。

  3. 在后面的时候,JVM的终结器线程将出队obj,调用它的finalize()方法,并且记录下该obj的终结器已经被调用。此时,obj被认为是被最终化的。

  4. 当垃圾收集器再次发现该obj是不可达的时,它将连同一切它所可达的(假定后者是不可达的)对象回收它的空间。

  注意,垃圾收集器至少需要两个周期(也许更多)来回收obj并且需要保留在该过程中所有另外的从obj可达的对象。如果一个程序员不小心,那么这可能会创建暂时的、微妙的和无法预言的资源保留问题。另外,JVM并不保证它将调用所有的已分配的可最终化的对象的终结器;它可能在垃圾收集器发现它们其中一些是不可达的之前就已退出。

    二、 在子类化时避免内存保留问题

  就算你不显式地使用它,最终化也可能延期资源的回收。请考虑下列实例:

public class RGBImage1 extends Image1 {
 private byte rgbData[];
}


  RGBImage1扩展Image1并且引入了新字段rgbData(也许还有一些方法,而本示例中却没有显示)。尽管你没有显式地在RGBImage1上定义一终结器,但是,这个类将自然地继承Image1的finalize()方法,并且所有的RGBImage1实例也将被认为是可最终化的。当一个RGBImage1实例成为不可达的,回收可能的很大的rgbData数组将被延迟直到该实例被终结(见图3)。这可能是一个很难发现的问题,因为该终结器可能是隐藏在一个很深的类层次上。

  一种避免这个问题的方法是重新安排代码,这样它可以使用"包含"来代替"扩展"模式,如下所示:

public class RGBImage2 {
 private Image1 img;
 private byte rgbData[];
 public void dispose() {img.dispose();}
}



图3.GC将因最终化而只排队Image1实例


  与RGBImage1相比,RGBImage2包含一个Image1的实例而不是扩展Image1。当RGBImage2的一个实例成为不可达时,垃圾回收器将即时回收它,连同rgbData数组(假定后者从任何其它地方都是不可达的),并且在最终化时将只排队Image1实例(见图4)。既然类RGBImage2并没有子类化Image1,那么它就不会从它中继承任何方法。因此,你可能必须把delegator方法添加到RGBImage1以存取要求的Image1中的方法(dispose()方法就是这样的一个例子)。

  然而,你不可能总是用上面描述的方式重新安排你的代码。在这种情况下,作为一个类用户,你必须做点多余的工作来确保当它们被终结时其实例并不占有多余的空间。下列代码说明实现方法:

public class RGBImage3 extends Image1 {
 private byte rgbData[];
 public void dispose() {
  super.dispose();
  rgbData = null;
 }
}



图4.在使用一个RGBImage3实例后调用dispose()


  RGBImage3与RGBImage1相同,但是添加了dispose()方法-它用来把rgbData字段置为null。你需要显式地在使用完一个RGBImage3实例之后调用dispose()以保证rgbData数组被即时回收(见图4)。我推荐在极少的场合下显式地把字段置为null;这里就是其中之一。


[首页]    [上一页]    [下一页]    [末页]    

文章来源:天极网
·Java开发技术十年的回顾与展望
·用java程序调用ffmpeg执行视频文件格式转换flv
·JavaBean与Enterprise JavaBean的区别
·Java开发人员的十大戒律
·JavaFX Script将终结AJAX?还是另一种选择?
·Eclipse五岁了:Java程序员的Eclipse情结
·审查Java代码的十一种常见错误
·Core JavaScript Guide
·Thinking In Java英文版
·在无线J2ME设备上实现超文本传输协议
 放生
 愚爱
 够爱
 触电
 白狐
 葬爱
 光荣
 画心
 火花
 稻香
 小酒窝
 下雨天
 右手边
 安静了
 魔杰座
 你不像她
 边做边爱
 擦肩而过
 我的答铃
 怀念过去
 等一分钟
 放手去爱
 冰河时代
 你的承诺
 自由飞翔
 原谅我一次
 吻的太逼真
 左眼皮跳跳
 做你的爱人
 一定要爱你
 飞向别人的床
 爱上别人的人
 感动天感动地
 心在跳情在烧
 玫瑰花的葬礼
 有没有人告诉你
 即使知道要见面
 爱上你是一个错
 最后一次的温柔
 爱上你是我的错
 怎么会狠心伤害我
 不是因为寂寞才想
 亲爱的那不是爱情
 难道爱一个人有错
 寂寞的时候说爱我