1 引言
??? 垃圾收集(Garbage Collection)是JAVA程序員在程序開發(fā)" title="程序開發(fā)">程序開發(fā)中感到最方便的一個特性,,使程序員擺脫了內(nèi)存管理的困擾。盡管JVM的垃圾收集器" title="收集器">收集器,,已經(jīng)在結(jié)構(gòu)和算法上作了相當(dāng)大的改進,,但在實際的應(yīng)用中,,尤其在大型的應(yīng)用軟件" title="應(yīng)用軟件">應(yīng)用軟件中,還是會碰到一些實際問題,。一個比較普遍的問題是,,一些已經(jīng)沒有用處的對象所占的內(nèi)存似乎難以釋放,在操作過程中,,內(nèi)存持續(xù)階梯式上升,,經(jīng)常在某個時候出現(xiàn)明顯的停頓,在感覺這次操作特別慢,,這是由于發(fā)生了一次完全的垃圾收集的結(jié)果,。導(dǎo)致垃圾收集效率的下降,甚至發(fā)生內(nèi)存泄漏的原因是多方面的" title="面的">面的,,但由于存在不恰當(dāng)?shù)膶ο笠靡约皬?fù)雜的對象引用關(guān)系,,是發(fā)生這個問題的一個重要的因素,同樣處理好對象引用關(guān)系也是解決這個問題的關(guān)鍵,。
2?對象引用分析
??? 在JAVA程序中,,被靜態(tài)(Static)變量和全局(Global)變量直接或間接引用的對象不能被垃圾收集器收集。假如一個對象被一個靜態(tài)變量引用,,即使該對象已經(jīng)沒有用處了,,但不能作為垃圾被收集,不僅如此,,該對象直接和間接引用的所有對象都不能被收集,。
??? 除了上述情況,在理論上,,不被靜態(tài)變量和全局變量直接或間接引用其他的所有對象,,即使它們之間存在著相互引用關(guān)系,,也可以被垃圾收集器收集。但是,,不管垃圾收集器如何工作,,對象是否被靜態(tài)變量和全局變量直接或間接引用,對象引用關(guān)系越復(fù)雜,,就需要花費更多的時間來處理,。因此,由于垃圾收集器結(jié)構(gòu)和算法上的局限,,對于一些引用關(guān)系復(fù)雜的對象,,需要經(jīng)過多次或完全的垃圾收集才可以收集,導(dǎo)致垃圾收集器消耗額外的資源,,影響垃圾收集的效率,;對于引用關(guān)系特別復(fù)雜的對象,垃圾收集器可能就根本沒有足夠的時間來處理,,容易造成內(nèi)存的泄漏,。
??? 為了說明對象的引用關(guān)系,,下面以對話框" title="對話框">對話框及其組件為例說明,。TestDialog從JDialog繼承,對話框中放置一個JButton按鈕,,按鈕添加了一個動作監(jiān)聽器(ActionListener),。
??? 以下是類的部分代碼:
??? 圖1為對話框和按鈕相關(guān)的主要對象的引用關(guān)系圖。這里方框表示對象實例(Instance)的類或類型,,其中TestDialog$1為TestDialog的匿名內(nèi)部類,,就是添加到按鈕的ActionListener監(jiān)聽器對象所對應(yīng)的類;連接線表示對象引用關(guān)系,,其中箭頭指向的對象被另一個對象直接引用,,連接線旁的文字表示引用著被引用對象的屬性(變量),如TestDialog對象直接引用了JButton對象,,JButton對象的引用保存在TestDialog的屬性testButton中,;Object數(shù)組把對象的引用作為元素存放。
??? 從對象引用關(guān)系圖中可以看出,,一個對象對另外一個對象的引用可能是直接的,,也可以通過其他對象的引用發(fā)生間接引用。在TestDialog對象和JRootPane對象的引用關(guān)系中,,通過屬性rootPane直接引用了JRootPane對象,;屬性component引用一個Object數(shù)組對象,而JRootPane對象又是Object數(shù)組的一個元素,,因而TestDialog對象又同時間接地引用了JRootPane對象,。
在圖1中,還可以看到一個普遍的現(xiàn)象,對象之間經(jīng)常存在著相互引用關(guān)系,,而且有時候存在多條的引用的路徑,,如TestDialog對象與JButton對象之間的相互引用。首先TestDialog對象中的testButton屬性直接引用了JButton對象,,同時,,通過容器和組件的關(guān)系,通過JRootPane,、JPanel等又存在間接的引用,;JButton對象反過來又引用TestDialog對象,即通過屬性parent對容器對象有引用,,對話框是對話框內(nèi)組件的頂層容器,,JButton對象通過容器和組件的關(guān)系實現(xiàn)對TestDialog對象的引用;另外,,JButton對象通過監(jiān)聽器列表對TestDialog$1內(nèi)部類的對象實例有引用,,而匿名內(nèi)部類對外部的類(即TestDialog)的對象實例有缺省的引用。
??? 單從圖1看,,這些對象的引用關(guān)系看起來還不太復(fù)雜,,實際上,很多對象本身的引用關(guān)系已經(jīng)非常復(fù)雜,,尤其是Swing組件,,這些組件內(nèi)部的對象引用比較多。以 JButton 為例,,為了清晰起見,,圖2的對象引用關(guān)系圖只畫出了與? 數(shù)據(jù)模型(Model) 的引用關(guān)系。
??? 除了與DefaultButtonModel 對象 的相互引用外,,通過 JButton 的屬性 layoutMgr與OverlayLayout的屬性target,,JButton和OverlayLayout的對象也形成相互引用;JButton對象還通過以下引用路徑,,最后又引用回到本身對象,,其中前面表示為類,括號內(nèi)是該類或父類中的屬性:
JButton (ActionMap actionMap)
-> ActionMap (ActionMap? parent)
-> ActionMapUIResource (AbstractAction$ArrayTable? arrayTable)
-> AbstractAction$ArrayTable (Object? table)
-> Object[] ()
-> BasicButtonListener$PressedAction (AbstractButton? b)
-> JButton,;
??? JButton與其他對象的引用路徑在這里不一一列舉,。
??? 在這個例子中,一些組件對象已經(jīng)存在比較復(fù)雜的引用關(guān)系,,在通過與另一些對象又形成相互引用,,組成了更加復(fù)雜的對象引用關(guān)系。在關(guān)閉對話框時,,如果不作特別的操作,,這些對象的引用關(guān)系將保持不變,,對垃圾收集的效率產(chǎn)生有很大的影響。
??? 在存在引用關(guān)系的所有對象中,,假如某個對象仍然是有用的,,或者不恰當(dāng)?shù)谋混o態(tài)變量和全局變量直接或間接引用,導(dǎo)致有引用關(guān)系的所有應(yīng)該成為垃圾的對象都無法被收集,,造成一定的內(nèi)存泄漏,。
3?解決方法
??? 為了提高垃圾收集的效率,必須要簡化對象的引用關(guān)系,,并及時清除靜態(tài)變量的引用以避免內(nèi)存泄漏,,具體可以通過以下幾個方面來完成。
3.1?清除直接對象引用
????當(dāng)一個對象不再被使用時,,應(yīng)該及時清除引用該對象的所有靜態(tài)變量,;同時,清除該對象中類型為對象的屬性,,若有必要,,則還應(yīng)該調(diào)用該屬性引用的對象的某個方法來清除內(nèi)存或釋放資源。如在對話框的例子中,,當(dāng)對話框關(guān)閉時,,應(yīng)該清除屬性testButton的引用,這時可以簡單的使用賦值語句:
??? testButton = null,;
??? 可以使對象的引用關(guān)系變得簡單些,。
3.2?調(diào)用對象的特定方法
??? 當(dāng)一個對象不再被使用時,,如果對象提供了用來清除引用或釋放資源的方法,,應(yīng)該調(diào)用這些方法,但注意調(diào)用的時機或順序,,避免引起異?,F(xiàn)象。這些方法包括對話框的dispose方法,,容器組件的remove方法,,Swing組件的UI的uninstall方法,移除監(jiān)聽器方法等,,也可以是某個類的本身定義的清除引用方法,。
??? 在對話框的例子中,對話框的dispose方法主要釋放一些與本地有關(guān)的資源,,若不調(diào)用,,將不能清除對話框的一個全局引用,造成內(nèi)存泄漏,;容器的remove方法,,該方法主要清除了容器的變量componet對數(shù)組的引用以及數(shù)組對子組件對象的引用,,同時也清除了子組件對象中 的變量parent對容器對象的引用,若清除容器中的所有組件,,則可簡單的調(diào)用removeAll方法清除;在JButton中,,可以調(diào)用setMode(null)來設(shè)置Model,,不僅清除了JButton對象中的model和changeListener對象引用,而且同時清除了DefaultButtonModel對 AbstractButton$ButtonChangeListener 監(jiān)聽器對象的間接引用,。 如果調(diào)用了上述的這些方法,,在對話框例子中,許多的對象引用被清除,,極大地簡化了各個對象之間的引用關(guān)系,。
3.3?慎重使用內(nèi)部類
??? 非靜態(tài)內(nèi)部類(包含一般的匿名內(nèi)部類)中,隱含著外部的類的對象實例的一個引用,,這個引用無法清除,。在對話框的例子中,匿名內(nèi)部類實際包含這樣的一個屬性(變量):
??? private final test.TestDialog this$0;
??? 這個屬性在內(nèi)部類中就是源代碼中使用的TestDialog.this,。由于該屬性是final修飾,,所有不允許再次賦值,即不可以清除,。同樣,,由于沒有使用變量來保存該監(jiān)聽器對象的引用,因此無法簡單地使用JButton的removeActionListener方法移除加在按鈕上的監(jiān)聽器,。為了清除上述的引用關(guān)系,,可以把匿名內(nèi)部類該寫為一個靜態(tài)內(nèi)部類,把對話框的實例作為該內(nèi)部類的構(gòu)造器的參數(shù)顯式地傳入,,同時在對話框中保存該內(nèi)部類的對象引用,。在清除引用時,既可以移除監(jiān)聽器,,也可以通過監(jiān)聽器變量清除內(nèi)部類的對話框引用,。原來的對話框部分代碼可以改為以下代碼,這時應(yīng)該使用dialog變量,,而不是??? TestDialog.this:
??
4?結(jié)束語
??? 通過各種方法清除對象的引用,,簡化了相關(guān)對象的引用關(guān)系,使得應(yīng)該成為垃圾的對象及時被收集而釋放內(nèi)存,,從而減少程序?qū)Σ僮飨到y(tǒng)的內(nèi)存需求,。在大型應(yīng)用軟件“永中Office”的實際應(yīng)用過程中,在處理對象引用關(guān)系復(fù)雜的情況時,,采用簡化對象引用關(guān)系的方法,,垃圾收集的問題已經(jīng)取得明顯的效果,。
參考文獻(xiàn)
?
1 [ISBN7-111-09635-5/TP-2242] Joshua Bloch 著.
2 Java 高效編程指南.北京:機械工業(yè)出版社,2002:3-12 .