版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡介
1、摘要雖然Java 虛擬機(jī)(JVM及其垃圾收集器(garbage collector,GC 負(fù)責(zé)管理大多數(shù)的內(nèi)存任務(wù),Java 軟件程序中還是有可能出現(xiàn)內(nèi)存泄漏。實(shí)際上,這在大型項(xiàng)目中是一個(gè)常見的問題。避免內(nèi)存泄漏的第一步是要弄清楚它是如何發(fā)生的。本文介紹了編寫Java 代碼的一些常見的內(nèi)存泄漏陷阱,以及編寫不泄漏代碼的一些最佳實(shí)踐。一旦發(fā)生了內(nèi)存泄漏,要指出造成泄漏的代碼是非常困難的。因此本文還介紹了一種新工具,用來診斷泄漏并指出根本原因。該工具的開銷非常小,因此可以使用它來尋找處于生產(chǎn)中的系統(tǒng)的內(nèi)存泄漏。垃圾收集器的作用雖然垃圾收集器處理了大多數(shù)內(nèi)存管理問題,從而使編程人員的生活變得更輕松了
2、,但是編程人員還是可能犯錯(cuò)而導(dǎo)致出現(xiàn)內(nèi)存問題。簡單地說,GC 循環(huán)地跟蹤所有來自“根”對(duì)象(堆棧對(duì)象、靜態(tài)對(duì)象、JNI 句柄指向的對(duì)象,諸如此類)的引用,并將所有它所能到達(dá)的對(duì)象標(biāo)記為活動(dòng)的。程序只可以操縱這些對(duì)象;其他的對(duì)象都被刪除了。因?yàn)镚C 使程序不可能到達(dá)已被刪除的對(duì)象,這么做就是安全的。雖然內(nèi)存管理可以說是自動(dòng)化的,但是這并不能使編程人員免受思考內(nèi)存管理問題之苦。例如,分配(以及釋放)內(nèi)存總會(huì)有開銷,雖然這種開銷對(duì)編程人員來說是不可見的。創(chuàng)建了太多對(duì)象的程序?qū)?huì)比完成同樣的功能而創(chuàng)建的對(duì)象卻比較少的程序更慢一些(在其他條件相同的情況下)。而且,與本文更為密切相關(guān)的是,如果忘記“釋放”
3、先前分配的內(nèi)存,就可能造成內(nèi)存泄漏。如果程序保留對(duì)永遠(yuǎn)不再使用的對(duì)象的引用,這些對(duì)象將會(huì)占用并耗盡內(nèi)存,這是因?yàn)樽詣?dòng)化的垃圾收集器無法證明這些對(duì)象將不再使用。正如我們先前所說的,如果存在一個(gè)對(duì)對(duì)象的引用,對(duì)象就被定義為活動(dòng)的,因此不能刪除。為了確保能回收對(duì)象占用的內(nèi)存,編程人員必須確保該對(duì)象不能到達(dá)。這通常是通過將對(duì)象字段設(shè)置為null 或者從集合(collection中移除對(duì)象而完成的。但是,注意,當(dāng)局部變量不再使用時(shí),沒有必要將其顯式地設(shè)置為null 。對(duì)這些變量的引用將隨著方法的退出而自動(dòng)清除。概括地說,這就是內(nèi)存托管語言中的內(nèi)存泄漏產(chǎn)生的主要原因:保留下來卻永遠(yuǎn)不再使用的對(duì)象引用。典型
4、泄漏既然我們知道了在Java 中確實(shí)有可能發(fā)生內(nèi)存泄漏,就讓我們來看一些典型的內(nèi)存泄漏及其原因。全局集合在大的應(yīng)用程序中有某種全局的數(shù)據(jù)儲(chǔ)存庫是很常見的,例如一個(gè)JNDI 樹或一個(gè)會(huì)話表。在這些情況下,必須注意管理儲(chǔ)存庫的大小。必須有某種機(jī)制從儲(chǔ)存庫中移除不再需要的數(shù)據(jù)。這可能有多種方法,但是最常見的一種是周期性運(yùn)行的某種清除任務(wù)。該任務(wù)將驗(yàn)證儲(chǔ)存庫中的數(shù)據(jù),并移除任何不再需要的數(shù)據(jù)。另一種管理儲(chǔ)存庫的方法是使用反向鏈接(referrer計(jì)數(shù)。然后集合負(fù)責(zé)統(tǒng)計(jì)集合中每個(gè)入口的反向鏈接的數(shù)目。這要求反向鏈接告訴集合何時(shí)會(huì)退出入口。當(dāng)反向鏈接數(shù)目為零時(shí),該元素就可以從集合中移除了。緩存緩存是一種數(shù)
5、據(jù)結(jié)構(gòu),用于快速查找已經(jīng)執(zhí)行的操作的結(jié)果。因此,如果一個(gè)操作執(zhí)行起來很慢,對(duì)于常用的輸入數(shù)據(jù),就可以將操作的結(jié)果緩存,并在下次調(diào)用該操作時(shí)使用緩存的數(shù)據(jù)。緩存通常都是以動(dòng)態(tài)方式實(shí)現(xiàn)的,其中新的結(jié)果是在執(zhí)行時(shí)添加到緩存中的。典型的算法是:檢查結(jié)果是否在緩存中,如果在,就返回結(jié)果。 如果結(jié)果不在緩存中,就進(jìn)行計(jì)算。 將計(jì)算出來的結(jié)果添加到緩存中,以便以后對(duì)該操作的調(diào)用可以使用。 該算法的問題(或者說是潛在的內(nèi)存泄漏)出在最后一步。如果調(diào)用該操作時(shí)有相當(dāng)多的不同輸入,就將有相當(dāng)多的結(jié)果存儲(chǔ)在緩存中。很明顯這不是正確的方法。為了預(yù)防這種具有潛在破壞性的設(shè)計(jì),程序必須確保對(duì)于緩存所使用的內(nèi)存容量有一個(gè)上
6、限。因此,更好的算法是:檢查結(jié)果是否在緩存中,如果在,就返回結(jié)果。 如果結(jié)果不在緩存中,就進(jìn)行計(jì)算。 如果緩存所占的空間過大,就移除緩存最久的結(jié)果。 將計(jì)算出來的結(jié)果添加到緩存中,以便以后對(duì)該操作的調(diào)用可以使用。 通過始終移除緩存最久的結(jié)果,我們實(shí)際上進(jìn)行了這樣的假設(shè):在將來,比起緩存最久的數(shù)據(jù),最近輸入的數(shù)據(jù)更有可能用到。這通常是一個(gè)不錯(cuò)的假設(shè)。 新算法將確保緩存的容量處于預(yù)定義的內(nèi)存范圍之內(nèi)。確切的范圍可能很難計(jì)算,因?yàn)榫彺嬷械膶?duì)象在不斷變化,而且它們的引用包羅萬象。為緩存設(shè)置正確的大小是一項(xiàng)非常復(fù)雜的任務(wù),需要將所使用的內(nèi)存容量與檢索數(shù)據(jù)的速度加以平衡。解決這個(gè)問題的另一種方法是使用ja
7、va.lang.ref.SoftReference 類跟蹤緩存中的對(duì)象。這種方法保證這些引用能夠被移除,如果虛擬機(jī)的內(nèi)存用盡而需要更多堆的話。ClassLoaderJava ClassLoader結(jié)構(gòu)的使用為內(nèi)存泄漏提供了許多可乘之機(jī)。正是該結(jié)構(gòu)本身的復(fù)雜性使ClassLoader 在內(nèi)存泄漏方面存在如此多的問題。ClassLoader 的特別之處在于它不僅涉及“常規(guī)”的對(duì)象引用,還涉及元對(duì)象引用,比如:字段、方法和類。這意味著只要有對(duì)字段、方法、類或ClassLoader 的對(duì)象的引用,ClassLoader 就會(huì)駐留在JVM 中。因?yàn)镃lassLoader 本身可以關(guān)聯(lián)許多類及其靜態(tài)字段,
8、所以就有許多內(nèi)存被泄漏了。確定泄漏的位置通常發(fā)生內(nèi)存泄漏的第一個(gè)跡象是:在應(yīng)用程序中出現(xiàn)了OutOfMemoryError 。這通常發(fā)生在您最不愿意它發(fā)生的生產(chǎn)環(huán)境中,此時(shí)幾乎不能進(jìn)行調(diào)試。有可能是因?yàn)闇y(cè)試環(huán)境運(yùn)行應(yīng)用程序的方式與生產(chǎn)系統(tǒng)不完全相同,因而導(dǎo)致泄漏只出現(xiàn)在生產(chǎn)中。在這種情況下,需要使用一些開銷較低的工具來監(jiān)控和查找內(nèi)存泄漏。還需要能夠無需重啟系統(tǒng)或修改代碼就可以將這些工具連接到正在運(yùn)行的系統(tǒng)上??赡茏钪匾氖牵?dāng)進(jìn)行分析時(shí),需要能夠斷開工具而保持系統(tǒng)不受干擾。雖然OutOfMemoryError 通常都是內(nèi)存泄漏的信號(hào),但是也有可能應(yīng)用程序確實(shí)正在使用這么多的內(nèi)存;對(duì)于后者,或者
9、必須增加JVM 可用的堆的數(shù)量,或者對(duì)應(yīng)用程序進(jìn)行某種更改,使它使用較少的內(nèi)存。但是,在許多情況下,OutOfMemoryError 都是內(nèi)存泄漏的信號(hào)。一種查明方法是不間斷地監(jiān)控GC 的活動(dòng),確定內(nèi)存使用量是否隨著時(shí)間增加。如果確實(shí)如此,就可能發(fā)生了內(nèi)存泄漏。詳細(xì)輸出有許多監(jiān)控垃圾收集器活動(dòng)的方法。而其中使用最廣泛的可能是使用-Xverbose:gc選項(xiàng)啟動(dòng)JVM ,并觀察輸出。memory 10.109-10.235: GC 65536K-16788K (65536K, 126.000 ms 箭頭后面的值(本例中是16788K )是垃圾收集所使用的堆的容量。 控制臺(tái)查看連續(xù)不斷的GC 的詳
10、細(xì)統(tǒng)計(jì)信息的輸出將是非常乏味的。幸好有這方面的工具。JRockit Management Console可以顯示堆使用量的圖示。借助于該圖,可以很容易地看出堆使用量是否隨時(shí)間增加。圖1. JRockit Management Console甚至可以配置該管理控制臺(tái),以便如果發(fā)生堆使用量過大的情況(或基于其他的事件),控制臺(tái)能夠向您發(fā)送電子郵件。這明顯使內(nèi)存泄漏的查看變得更容易了。內(nèi)存泄漏檢測(cè)工具還有其他的專門進(jìn)行內(nèi)存泄漏檢測(cè)的工具。JRockit Memory Leak Detector可以用來查看內(nèi)存泄漏,并可以更深入地查出泄漏的根源。這個(gè)強(qiáng)大的工具是緊密集成到JRockit JVM中的,其
11、開銷非常小,對(duì)虛擬機(jī)的堆的訪問也很容易。 專業(yè)工具的優(yōu)點(diǎn)一旦知道確實(shí)發(fā)生了內(nèi)存泄漏,就需要更專業(yè)的工具來查明為什么會(huì)發(fā)生泄漏。JVM 自己是不會(huì)告訴您的。這些專業(yè)工具從JVM 獲得內(nèi)存系統(tǒng)信息的方法基本上有兩種:JVMTI 和字節(jié)碼技術(shù)(byte code instrumentation。Java 虛擬機(jī)工具接口(Java Virtual Machine Tools Interface,JVMTI 及其前身Java 虛擬機(jī)監(jiān)視程序接口(Java Virtual Machine Profiling Interface,JVMPI 是外部工具與JVM 通信并從JVM 收集信息的標(biāo)準(zhǔn)化接口。字節(jié)碼技
12、術(shù)是指使用探測(cè)器處理字節(jié)碼以獲得工具所需的信息的技術(shù)。對(duì)于內(nèi)存泄漏檢測(cè)來說,這兩種技術(shù)有兩個(gè)缺點(diǎn),這使它們不太適合用于生產(chǎn)環(huán)境。首先,它們?cè)趦?nèi)存占用和性能降低方面的開銷不可忽略。有關(guān)堆使用量的信息必須以某種方式從JVM 導(dǎo)出,并收集到工具中進(jìn)行處理。這意味著要為工具分配內(nèi)存。信息的導(dǎo)出也影響了JVM 的性能。例如,當(dāng)收集信息時(shí),垃圾收集器將運(yùn)行得比較慢。另外一個(gè)缺點(diǎn)是需要始終將工具連在JVM 上。這是不可能的:將工具連在一個(gè)已經(jīng)啟動(dòng)的JVM 上,進(jìn)行分析,斷開工具,并保持JVM 運(yùn)行。因?yàn)镴Rockit Memory Leak Detector是集成到JVM 中的,就沒有這兩個(gè)缺點(diǎn)了。首先,許
13、多處理和分析工作是在JVM 內(nèi)部進(jìn)行的,所以沒有必要轉(zhuǎn)換或重新創(chuàng)建任何數(shù)據(jù)。處理還可以背負(fù)(piggyback在垃圾收集器本身上而進(jìn)行,這意味著提高了速度。其次,只要JVM 是使用-Xmanagement 選項(xiàng)(允許通過遠(yuǎn)程JMX 接口監(jiān)控和管理JVM )啟動(dòng)的,Memory Leak Detector就可以與運(yùn)行中的JVM 進(jìn)行連接或斷開。當(dāng)該工具斷開時(shí),沒有任何東西遺留在JVM 中,JVM 又將以全速運(yùn)行代碼,正如工具連接之前一樣。趨勢(shì)分析讓我們深入地研究一下該工具以及它是如何用來跟蹤內(nèi)存泄漏的。在知道發(fā)生內(nèi)存泄漏之后,第一步是要弄清楚泄漏了什么數(shù)據(jù)-哪個(gè)類的對(duì)象引起了泄漏?JRockit
14、 Memory Leak Detector是通過在每次垃圾收集時(shí)計(jì)算每個(gè)類的現(xiàn)有對(duì)象的數(shù)目來實(shí)現(xiàn)這一步的。如果特定類的對(duì)象數(shù)目隨時(shí)間而增長(“增長率”),就可能發(fā)生了內(nèi)存泄漏。圖2. Memory Leak Detector的趨勢(shì)分析視圖因?yàn)樾孤┛赡芟窦?xì)流一樣非常小,所以趨勢(shì)分析必須運(yùn)行很長一段時(shí)間。在短時(shí)間內(nèi),可能會(huì)發(fā)生一些類的局部增長,而之后它們又會(huì)跌落。但是趨勢(shì)分析的開銷很?。ㄗ畲箝_銷也不過是在每次垃圾收集時(shí)將數(shù)據(jù)包由JRockit 發(fā)送到Memory Leak Detector)。開銷不應(yīng)該成為任何系統(tǒng)的問題即使是一個(gè)全速運(yùn)行的生產(chǎn)中的系統(tǒng)。起初數(shù)目會(huì)跳躍不停,但是一段時(shí)間之后它們就會(huì)
15、穩(wěn)定下來,并顯示出哪些類的數(shù)目在增長。找出根本原因有時(shí)候知道是哪些類的對(duì)象在泄漏就足以說明問題了。這些類可能只用于代碼中的非常有限的部分,對(duì)代碼進(jìn)行一次快速檢查就可以顯示出問題所在。遺憾地是,很有可能只有這類信息還并不夠。例如,常見到泄漏出在類java.lang.String 的對(duì)象上,但是因?yàn)樽址谡麄€(gè)程序中都使用,所以這并沒有多大幫助。 我們想知道的是,另外還有哪些對(duì)象與泄漏對(duì)象關(guān)聯(lián)?在本例中是String 。為什么泄漏的對(duì)象還存在?哪些對(duì)象保留了對(duì)這些對(duì)象的引用?但是能列出的所有保留對(duì)String 的引用的對(duì)象將會(huì)非常多,以至于沒有什么實(shí)際用處。為了限制數(shù)據(jù)的數(shù)量,可以將數(shù)據(jù)按類分組,
16、以便可以看出其他哪些對(duì)象的類與泄漏對(duì)象(String關(guān)聯(lián)。例如,String 在Hashtable 中是很常見的,因此我們可能會(huì)看到與String 關(guān)聯(lián)的Hashtable 數(shù)據(jù)項(xiàng)對(duì)象。由Hashtable 數(shù)據(jù)項(xiàng)倒推,我們最終可以找到與這些數(shù)據(jù)項(xiàng)有關(guān)的Hashtable 對(duì)象以及String (如圖3所示)。 圖 3. 在工具中看到的類型圖的示例視圖 倒推 因?yàn)槲覀內(nèi)匀皇且灶惖膶?duì)象而不是單獨(dú)的對(duì)象來看待對(duì)象, 所以我們不知道 是哪個(gè) Hashtable 在泄漏。 如果我們可以弄清楚系統(tǒng)中所有的 Hashtable 都有多 大, 我們就可以假定最大的 Hashtable 就是正在泄漏的那一個(gè)(
17、因?yàn)殡S著時(shí)間的 流逝它會(huì)累積泄漏而增長得相當(dāng)大)。因此,一份有關(guān)所有 Hashtable 對(duì)象以及 它們引用了多少數(shù)據(jù)的列表,將會(huì)幫助我們指出造成泄漏的確切 Hashtabl。 圖 4. 界面:Hashtable 對(duì)象以及它們所引用數(shù)據(jù)的數(shù)量的列表 對(duì)對(duì)象引用數(shù)據(jù)數(shù)目的計(jì)算開銷非常大 (需要以該對(duì)象作為根遍歷引用圖) , 如果必須對(duì)許多對(duì)象都這么做, 將會(huì)花很多時(shí)間。如果了解一點(diǎn) Hashtable 的內(nèi) 部實(shí)現(xiàn)原理就可以找到一條捷徑。Hashtable 的內(nèi)部有一個(gè) Hashtable 數(shù)據(jù)項(xiàng)的 數(shù)組。該數(shù)組隨著 Hashtable 中對(duì)象數(shù)目的增長而增長。因此,為找出最大的 Hashtab
18、le,我們只需找出引用 Hashtable 數(shù)據(jù)項(xiàng)的最大數(shù)組。這樣要快很多。 圖 5. 界面:最大的 Hashtable 數(shù)據(jù)項(xiàng)數(shù)組及其大小的清單 更進(jìn)一步 當(dāng)找到發(fā)生泄漏的 Hashtable 實(shí)例時(shí), 我們可以看到其他哪些實(shí)例在引用該 Hashtable,并倒推回去看看是哪個(gè) Hashtable 在泄漏。 圖 6. 這就是工具中的實(shí)例圖 例如,該 Hashtable 可能是由 MyServer 類型的對(duì)象在名為 activeSessions 的字段中引用的。這種信息通常就足以查找源代碼以定位問題所在了。 圖 7. 檢查對(duì)象以及它對(duì)其他對(duì)象的引用 找出分配位置 當(dāng)跟蹤內(nèi)存泄漏問題時(shí), 查看對(duì)象分配到哪里是很有用的。只知道它們?nèi)绾?與其他對(duì)象相關(guān)聯(lián)(即哪些對(duì)象引用了它們)是不夠的,關(guān)于它們?cè)诤翁巹?chuàng)建的 信息也很有用。當(dāng)然了,您并不想創(chuàng)建應(yīng)用程序的輔助構(gòu)件,以打印每次分配的 堆棧跟蹤(st
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 2025農(nóng)村信用合作社聘用合同樣本
- 二零二五年度國際貨物出口合同范文:非洲新興市場(chǎng)合作項(xiàng)目
- 2025年度公廁工程承包合同書(含社區(qū)共建)3篇
- 二零二五年度公司施工隊(duì)鋼結(jié)構(gòu)工程施工合作協(xié)議3篇
- 二零二五年度全新高空橋梁施工意外事故免責(zé)責(zé)任書3篇
- 二零二五年度智能倉儲(chǔ)物流系統(tǒng)采購合同模板2篇
- 二零二五年度消防隊(duì)伍后勤保障服務(wù)合同3篇
- 2025年度農(nóng)村出租房租賃與農(nóng)村電子商務(wù)運(yùn)營服務(wù)合同
- 2025年度智慧城市建設(shè)項(xiàng)目合同2篇
- 二零二五年度農(nóng)村集體土地房屋產(chǎn)權(quán)轉(zhuǎn)讓合同下載
- 2023家居日用品市場(chǎng)洞察報(bào)告分析
- 臨床合理用藥的持續(xù)改進(jìn)措施
- 修理廠自查自糾報(bào)告 自查自糾報(bào)告
- 中學(xué)推優(yōu)入團(tuán)方案
- 粉末涂料有限公司除塵系統(tǒng)安全風(fēng)險(xiǎn)分級(jí)清單
- 招投標(biāo)專員績效考核表
- SL631-637-2012-水利水電工程單元工程施工質(zhì)量驗(yàn)收評(píng)定標(biāo)準(zhǔn)
- 深圳分區(qū)地圖可移動(dòng)編輯
- 2023年新版烏斯特統(tǒng)計(jì)公報(bào)即將發(fā)布
- 污水處理廠安全生產(chǎn)風(fēng)險(xiǎn)分級(jí)管控體系方案全套資料匯編完整版
- 高危急性胸痛的快速診斷和誤診病案分析
評(píng)論
0/150
提交評(píng)論