《电子技术应用》
您所在的位置:首页 > 其他 > 业界动态 > Java程序中提高垃圾收集效率的方法

Java程序中提高垃圾收集效率的方法

2009-09-01
作者:吴良巧

  摘  要: 针对复杂的对象引用关系影响垃圾收集效率的问题,分析了对象之间存在的引用关系,提出了简化对象引用关系的解决方法。

  关键词: 垃圾收集  对象引用  Java语言

 

  垃圾收集(Garbage Collection)是Java程序员在程序开发中感到最方便的一个特性,它使程序员摆脱了内存管理的困扰。尽管JVM的垃圾收集器已经在结构和算法上作了相当大的改进,但在实际应用中,尤其在大型的应用软件中,还是会碰到一些实际问题。一个比较普遍的问题是,一些已经没用的对象所占的内存难以释放。在操作过程中,内存持续阶梯式上升,经常在某个时候出现明显的停顿,并感觉这次操作特别慢,这是由于发生了一次完全的垃圾收集的结果。导致垃圾收集效率下降、甚至发生内存泄漏的原因是多方面的。由于存在不恰当的对象引用以及复杂的对象引用关系是发生这个问题的重要因素,同样处理好对象引用关系也是解决这个问题的关键。

1  对象引用分析

  在Java程序中,被静态(Static)变量和全局(Global)变量直接或间接引用的对象不能被垃圾收集器收集。假如一个对象被一个静态变量引用,即使该对象已经没有用处了,也不能作为垃圾被收集。不仅如此,该对象直接和间接引用的所有对象都不能被收集。

  除了上述情况,理论上不可被静态变量和全局变量直接或间接引用其他所有对象,即使它们之间存在着相互引用关系,也可以被垃圾收集器收集。但是,不管垃圾收集器如何工作、对象是否被静态变量和全局变量直接或间接引用,对象引用关系越复杂,处理时花费的时间就越多。因此,由于垃圾收集器结构和算法上的局限,对于一些引用关系复杂的对象,需要经过多次或完全的垃圾收集才可以收集。这将导致垃圾收集器消耗额外的资源,影响垃圾收集的效率。对于引用关系特别复杂的对象,垃圾收集器可能根本没有足够的时间来处理,从而容易造成内存的泄漏。

  为了说明对象的引用关系,下面以对话框及其组件为例说明。TestDialog从JDialog继承,对话框中放置一个JButton按钮,按钮添加了一个动作监听器(ActionListener)。以下是类的部分代码:

  

  图1为对话框和按钮相关的主要对象的引用关系图。图中方框表示对象实例(Instance)的类或类型,其中TestDialog$1为TestDialog的匿名内部类,就是添加到按钮的ActionListener监听器对象所对应的类;连接线表示对象引用关系,其中箭头指向的对象被另一个对象直接引用,连接线旁的文字表示引用了被引用对象的属性(变量),如TestDialog对象直接引用了JButton对象,JButton对象的引用保存在TestDialog的属性testButton中;Object数组把对象的引用作为元素存放。

 

  从图1可以看出:一个对象对另外一个对象的引用可以是直接的,也可以通过其他对象的引用发生间接引用。在TestDialog对象和JRootPane对象的引用关系中,通过属性rootPane直接引用了JRootPane对象;属性component引用一个Object数组对象,而JRootPane对象又是Object数组的一个元素,因而TestDialog对象又同时间接地引用了JRootPane对象。

  从图1中还可以看到一个普遍的现象,即对象之间经常存在着相互引用关系,而且有时候存在多条的引用路径,如TestDialog对象与JButton对象之间的相互引用。首先TestDialog对象中的testButton属性直接引用了JButton对象,同时,通过容器和组件的关系,使得JRootPane、JPanel等又存在间接的引用;JButton对象反过来又引用TestDialog对象。即通过属性parent对容器对象进行引用,对话框是对话框内组件的顶层容器,JButton对象通过容器和组件的关系实现对TestDialog对象的引用。另外,JButton对象通过监听器列表对TestDialog$1内部类的对象实例进行引用,而匿名内部类对外部类(即TestDialog)的对象实例有缺省的引用。

  单从图1看,这些对象的引用关系似乎不太复杂,但实际上很多对象本身的引用关系已经非常复杂,尤其是Swing组件。这些组件内部的对象引用比较多。图2是以JButton为例的对象引用关系图。为了清晰,图中只给出了与数据模型(Model)的引用关系。

 

    除了与DefaultButtonModel对象的相互引用外,通过JButton的属性layoutMgr与OverlayLayout的属性target,JButton和OverlayLayout的对象也形成相互引用。JButton对象还通过以下引用路径,最后又引用回到本身对象。其中前面表示为类,括号内是该类或父类中的属性。

  JButton(ActionMap actionMap)

  -> ActionMap(ActionMap parent)

  -> ActionMapUIResource(AbstractAction$ArrayTable

  arrayTable)

  -> AbstractAction$ArrayTable(Object table)

  -> Object[] ()

  -> BasicButtonListener$PressedAction(AbstractButton b)

  -> JButton;

  JButton与其他对象的引用路径在这里不一一列举。

  在这个示例中,一些组件对象已经存在比较复杂的引用关系,通过与另一些对象形成的相互引用,又组成了更加复杂的对象引用关系。在关闭对话框时,如果不作特别的操作,这些对象的引用关系将保持不变,从而对垃圾收集的效率产生很大的影响。

在存在引用关系的所有对象中,假如某个对象仍然有用或者不恰当地被静态变量和全局变量直接或间接引用,将导致有引用关系的所有应该成为垃圾的对象无法被收集,从而造成一定的内存泄漏。

2 解决方法

  为了提高垃圾收集的效率,必须简化对象的引用关系,并及时清除静态变量的引用以避免内存泄漏。具体方法可以通过以下几个方面来实现。

2.1 清除直接对象引用

  当一个对象不再被使用时,应该及时清除引用该对象的所有静态变量,同时,清除该对象中类型为对象的属性。若有必要,则还应该调用该属性引用的对象的某个方法来清除内存或释放资源。如在对话框的例子中,当对话框关闭时,应该清除属性testButton的引用,这时可以简单地使用赋值语句“testButton=null;”,从而使对象的引用关系变得简单。

2.2 调用对象的特定方法

  当一个对象不再被使用时,如果对象提供了用来清除引用或释放资源的方法,则应该调用这些方法,但要注意调用的时机或顺序,避免引起异常现象。这些方法包括对话框的Dispose方法、容器组件的Remove方法、Swing组件UI的Uninstall方法、移除监听器方法等,也可以是某个类本身定义的清除引用方法。

  在对话框的例子中,对话框的Dispose方法主要释放一些与本地有关的资源,若不调用,将不能清除对话框的一个全局引用,造成内存泄漏。容器的Remove方法主要清除了容器的变量Componet对数组的引用以及数组对子组件对象的引用,同时也清除了子组件对象中的变量Parent对容器对象的引用。若清除容器中的所有组件,则可简单地调用RemoveAll方法清除。在JButton中,可以调用SetMode(null)来设置Model。这样,不仅清除了JButton对象中的Model和ChangeListener对象引用,而且同时清除了DefaultButtonModel对AbstractButton$ButtonChangeListener监听器对象的间接引用。如果调用了上述的这些方法,则在对话框例子中,许多对象引用被清除,可以极大地简化各个对象之间的引用关系。

2.3 慎重使用内部类

  非静态内部类(包含一般的匿名内部类)中,隐含着外部类的对象实例的一个引用,这个引用无法清除。在对话框的例子中,匿名内部类实际包含这样的一个属性(变量):

  private final TestDialog this$0;

  这个属性在内部类中就是源代码中使用的TestDialog.this。由于该属性是final修饰,所以不允许再次赋值,不可以清除。同样,由于没有使用变量来保存该监听器对象的引用,因此无法简单地使用JButton的RemoveActionListener方法移除加在按钮上的监听器。为了清除上述的引用关系,可以把匿名内部类改写为一个静态内部类,把对话框的实例作为该内部类的构造器的参数显式地传入,同时在对话框中保存该内部类的对象引用。在清除引用时,既可以移除监听器,也可以通过监听器变量清除内部类的对话框引用。原来的对话框部分代码可以改为以下代码,这时应该使用dialog变量,而不是TestDialog.this:

  

3  结束语

  通过各种方法清除对象的引用,可以简化相关对象的引用关系,使得应该成为垃圾的对象及时被收集以释放内存,从而减少程序对操作系统的内存需求。在大型应用软件“永中Office”的实际应用过程中,在处理对象引用关系复杂的情况时,采用了简化对象引用关系的方法,使垃圾收集效率得到明显的提高。

 

参考文献

1 Bloch J.Java高效编程指南.北京:机械工业出版社,2002

本站内容除特别声明的原创文章之外,转载内容只为传递更多信息,并不代表本网站赞同其观点。转载的所有的文章、图片、音/视频文件等资料的版权归版权所有权人所有。本站采用的非本站原创文章及图片等内容无法一一联系确认版权者。如涉及作品内容、版权和其它问题,请及时通过电子邮件或电话通知我们,以便迅速采取适当措施,避免给双方造成不必要的经济损失。联系电话:010-82306118;邮箱:aet@chinaaet.com。