Java內(nèi)存泄漏的檢測(cè)和處理
如何查找引起內(nèi)存泄漏的原因一般有兩個(gè)步驟:第一是安排有經(jīng)驗(yàn)的編程人員對(duì)代碼進(jìn)行走查和分析,找出內(nèi)存泄漏發(fā)生的位置;第二是使用專門(mén)的內(nèi)存泄漏測(cè)試工具進(jìn)行測(cè)試。
第一個(gè)步驟在代碼走查的工作中,可以安排對(duì)系統(tǒng)業(yè)務(wù)和開(kāi)發(fā)語(yǔ)言工具比較熟悉的開(kāi)發(fā)人員對(duì)應(yīng)用的代碼進(jìn)行了交叉走查,盡量找出代碼中存在的數(shù)據(jù)庫(kù)連接聲明和結(jié)果集未關(guān)閉、代碼冗余等故障代碼。
第二個(gè)步驟就是檢測(cè)Java的內(nèi)存泄漏。在這里我們通常使用一些工具來(lái)檢查Java程序的內(nèi)存泄漏問(wèn)題。市場(chǎng)上已有幾種專業(yè)檢查Java內(nèi)存泄漏的工具,它們的基本工作原理大同小異,都是通過(guò)監(jiān)測(cè)Java程序運(yùn)行時(shí),所有對(duì)象的申請(qǐng)、釋放等動(dòng)作,將內(nèi)存管理的所有信息進(jìn)行統(tǒng)計(jì)、分析、可視化。開(kāi)發(fā)人員將根據(jù)這些信息判斷程序是否有內(nèi)存泄漏問(wèn)題。這些工具包括Optimizeit Profiler,JProbe Profiler,JinSight , Rational 公司的Purify等。
檢測(cè)內(nèi)存泄漏的存在
這里我們將簡(jiǎn)單介紹我們?cè)谑褂肙ptimizeit檢查的過(guò)程。通常在知道發(fā)生內(nèi)存泄漏之后,第一步是要弄清楚泄漏了什么數(shù)據(jù)和哪個(gè)類的對(duì)象引起了泄漏。
一般說(shuō)來(lái),一個(gè)正常的系統(tǒng)在其運(yùn)行穩(wěn)定后其內(nèi)存的占用量是基本穩(wěn)定的,不應(yīng)該是無(wú)限制的增長(zhǎng)的。同樣,對(duì)任何一個(gè)類的對(duì)象的使用個(gè)數(shù)也有一個(gè)相對(duì)穩(wěn)定的上限,不應(yīng)該是持續(xù)增長(zhǎng)的。根據(jù)這樣的基本假設(shè),我們持續(xù)地觀察系統(tǒng)運(yùn)行時(shí)使用的內(nèi)存的大小和各實(shí)例的個(gè)數(shù),如果內(nèi)存的大小持續(xù)地增長(zhǎng),則說(shuō)明系統(tǒng)存在內(nèi)存泄漏,如果特定類的實(shí)例對(duì)象個(gè)數(shù)隨時(shí)間而增長(zhǎng)(就是所謂的“增長(zhǎng)率”),則說(shuō)明這個(gè)類的實(shí)例可能存在泄漏情況。
另一方面通常發(fā)生內(nèi)存泄漏的第一個(gè)跡象是:在應(yīng)用程序中出現(xiàn)了OutOfMemoryError。在這種情況下,需要使用一些開(kāi)銷(xiāo)較低的工具來(lái)監(jiān)控和查找內(nèi)存泄漏。雖然OutOfMemoryError也有可能應(yīng)用程序確實(shí)正在使用這么多的內(nèi)存;對(duì)于這種情況則可以增加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)存泄漏。
處理內(nèi)存泄漏的方法
一旦知道確實(shí)發(fā)生了內(nèi)存泄漏,就需要更專業(yè)的工具來(lái)查明為什么會(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é)碼技術(shù)是指使用探測(cè)器處理字節(jié)碼以獲得工具所需的信息的技術(shù)。
Optimizeit是Borland公司的產(chǎn)品,主要用于協(xié)助對(duì)軟件系統(tǒng)進(jìn)行代碼優(yōu)化和故障診斷,其中的Optimizeit Profiler主要用于內(nèi)存泄漏的分析。Profiler的堆視圖就是用來(lái)觀察系統(tǒng)運(yùn)行使用的內(nèi)存大小和各個(gè)類的實(shí)例分配的個(gè)數(shù)的。
首先,Profiler會(huì)進(jìn)行趨勢(shì)分析,找出是哪個(gè)類的對(duì)象在泄漏。系統(tǒng)運(yùn)行長(zhǎng)時(shí)間后可以得到四個(gè)內(nèi)存快照。對(duì)這四個(gè)內(nèi)存快照進(jìn)行綜合分析,如果每一次快照的內(nèi)存使用都比上一次有增長(zhǎng),可以認(rèn)定系統(tǒng)存在內(nèi)存泄漏,找出在四個(gè)快照中實(shí)例個(gè)數(shù)都保持增長(zhǎng)的類,這些類可以初步被認(rèn)定為存在泄漏。通過(guò)數(shù)據(jù)收集和初步分析,可以得出初步結(jié)論:系統(tǒng)是否存在內(nèi)存泄漏和哪些對(duì)象存在泄漏(被泄漏)。
接下來(lái),看看有哪些其他的類與泄漏的類的對(duì)象相關(guān)聯(lián)。前面已經(jīng)談到Java中的內(nèi)存泄漏就是無(wú)用的對(duì)象保持,簡(jiǎn)單地說(shuō)就是因?yàn)榫幋a的錯(cuò)誤導(dǎo)致了一條本來(lái)不應(yīng)該存在的引用鏈的存在(從而導(dǎo)致了被引用的對(duì)象無(wú)法釋放),因此內(nèi)存泄漏分析的任務(wù)就是找出這條多余的引用鏈,并找到其形成的原因。查看對(duì)象分配到哪里是很有用的。同時(shí)只知道它們?nèi)绾闻c其他對(duì)象相關(guān)聯(lián)(即哪些對(duì)象引用了它們)是不夠的,關(guān)于它們?cè)诤翁巹?chuàng)建的信息也很有用。
最后,進(jìn)一步研究單個(gè)對(duì)象,看看它們是如何互相關(guān)聯(lián)的。借助于Profiler工具,應(yīng)用程序中的代碼可以在分配時(shí)進(jìn)行動(dòng)態(tài)添加,以創(chuàng)建堆棧跟蹤。也有可以對(duì)系統(tǒng)中所有對(duì)象分配進(jìn)行動(dòng)態(tài)的堆棧跟蹤。這些堆棧跟蹤可以在工具中進(jìn)行累積和分析。對(duì)每個(gè)被泄漏的實(shí)例對(duì)象,必然存在一條從某個(gè)牽引對(duì)象出發(fā)到達(dá)該對(duì)象的引用鏈。處于堆棧空間的牽引對(duì)象在被從棧中彈出后就失去其牽引的能力,變?yōu)榉菭恳龑?duì)象。因此,在長(zhǎng)時(shí)間的運(yùn)行后,被泄露的對(duì)象基本上都是被作為類的靜態(tài)變量的牽引對(duì)象牽引。
總而言之, Java雖然有自動(dòng)回收管理內(nèi)存的功能,但內(nèi)存泄漏也是不容忽視,它往往是破壞系統(tǒng)穩(wěn)定性的重要因素。
【編輯推薦】