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