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

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

2008-12-29
作者:吴良巧

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

2 对象引用分析
    在JAVA程序中,被静态(Static)变量和全局(Global)变量直接或间接引用的对象不能被垃圾收集器收集。假如一个对象被一个静态变量引用,即使该对象已经没有用处了,但不能作为垃圾被收集,不仅如此,该对象直接和间接引用的所有对象都不能被收集。
    除了上述情况,在理论上,不被静态变量和全局变量直接或间接引用其他的所有对象,即使它们之间存在着相互引用关系,也可以被垃圾收集器收集。但是,不管垃圾收集器如何工作,对象是否被静态变量和全局变量直接或间接引用,对象引用关系越复杂,就需要花费更多的时间来处理。因此,由于垃圾收集器结构和算法上的局限,对于一些引用关系复杂的对象,需要经过多次或完全的垃圾收集才可以收集,导致垃圾收集器消耗额外的资源,影响垃圾收集的效率;对于引用关系特别复杂的对象,垃圾收集器可能就根本没有足够的时间来处理,容易造成内存的泄漏。
    为了说明对象的引用关系,下面以对话框" title="对话框">对话框及其组件为例说明。TestDialog从JDialog继承,对话框中放置一个JButton按钮,按钮添加了一个动作监听器(ActionListener)。
    以下是类的部分代码:

    图1为对话框和按钮相关的主要对象的引用关系图。这里方框表示对象实例(Instance)的类或类型,其中TestDialog$1为TestDialog的匿名内部类,就是添加到按钮的ActionListener监听器对象所对应的类;连接线表示对象引用关系,其中箭头指向的对象被另一个对象直接引用,连接线旁的文字表示引用着被引用对象的属性(变量),如TestDialog对象直接引用了JButton对象,JButton对象的引用保存在TestDialog的属性testButton中;Object数组把对象的引用作为元素存放。
    从对象引用关系图中可以看出,一个对象对另外一个对象的引用可能是直接的,也可以通过其他对象的引用发生间接引用。在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组件,这些组件内部的对象引用比较多。以 JButton 为例,为了清晰起见,图2的对象引用关系图只画出了与  数据模型(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与其他对象的引用路径在这里不一一列举。
    在这个例子中,一些组件对象已经存在比较复杂的引用关系,在通过与另一些对象又形成相互引用,组成了更加复杂的对象引用关系。在关闭对话框时,如果不作特别的操作,这些对象的引用关系将保持不变,对垃圾收集的效率产生有很大的影响。
    在存在引用关系的所有对象中,假如某个对象仍然是有用的,或者不恰当的被静态变量和全局变量直接或间接引用,导致有引用关系的所有应该成为垃圾的对象都无法被收集,造成一定的内存泄漏。


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

3.1 清除直接对象引用

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

3.2 调用对象的特定方法

    当一个对象不再被使用时,如果对象提供了用来清除引用或释放资源的方法,应该调用这些方法,但注意调用的时机或顺序,避免引起异常现象。这些方法包括对话框的dispose方法,容器组件的remove方法,Swing组件的UI的uninstall方法,移除监听器方法等,也可以是某个类的本身定义的清除引用方法。
    在对话框的例子中,对话框的dispose方法主要释放一些与本地有关的资源,若不调用,将不能清除对话框的一个全局引用,造成内存泄漏;容器的remove方法,该方法主要清除了容器的变量componet对数组的引用以及数组对子组件对象的引用,同时也清除了子组件对象中 的变量parent对容器对象的引用,若清除容器中的所有组件,则可简单的调用removeAll方法清除;在JButton中,可以调用setMode(null)来设置Model,不仅清除了JButton对象中的model和changeListener对象引用,而且同时清除了DefaultButtonModel对 AbstractButton$ButtonChangeListener 监听器对象的间接引用。 如果调用了上述的这些方法,在对话框例子中,许多的对象引用被清除,极大地简化了各个对象之间的引用关系。

3.3 慎重使用内部类

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

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


参考文献
 
1 [ISBN7-111-09635-5/TP-2242] Joshua Bloch 著.

2 Java 高效编程指南.北京:机械工业出版社,2002:3-12 .

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