<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    關(guān)注技術(shù),關(guān)注生活

    任何事情只要開始去做,永遠(yuǎn)不會太遲。
    posts - 5, comments - 23, trackbacks - 0, articles - 18
      BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

    [轉(zhuǎn)]探討JAVA內(nèi)存泄漏原因和檢測工具

    Posted on 2006-12-09 13:19 errorfun 閱讀(845) 評論(0)  編輯  收藏 所屬分類: JAVA
    原文出處:http://dev2dev.bea.com/pub/a/2005/06/memory_leaks.html

    垃圾收集器的作用
    ??????? 雖然垃圾收集器處理了大多數(shù)內(nèi)存管理問題,從而使編程人員的生活變得更輕松了,但是編程人員還是可能犯錯而導(dǎo)致出現(xiàn)內(nèi)存問題。簡單地說,GC循環(huán)地跟蹤所有來自“根”對象(堆棧對象、靜態(tài)對象、JNI句柄指向的對象,諸如此類)的引用,并將所有它所能到達(dá)的對象標(biāo)記為活動的。程序只可以操縱這些對象;其他的對象都被刪除了。因?yàn)镚C使程序不可能到達(dá)已被刪除的對象,這么做就是安全的。

      雖然內(nèi)存管理可以說是自動化的,但是這并不能使編程人員免受思考內(nèi)存管理問題之苦。例如,分配(以及釋放)內(nèi)存總會有開銷,雖然這種開銷對編程人員來說是不可見的。創(chuàng)建了太多對象的程序?qū)韧瓿赏瑯拥墓δ芏鴦?chuàng)建的對象卻比較少的程序更慢一些(在其他條件相同的情況下)。

      而且,與本文更為密切相關(guān)的是,如果忘記“釋放”先前分配的內(nèi)存,就可能造成內(nèi)存泄漏。如果程序保留對永遠(yuǎn)不再使用的對象的引用,這些對象將會占用并耗盡內(nèi)存,這是因?yàn)樽詣踊睦占鳠o法證明這些對象將不再使用。正如我們先前所說的,如果存在一個對對象的引用,對象就被定義為活動的,因此不能刪除。為了確保能回收對象占用的內(nèi)存,編程人員必須確保該對象不能到達(dá)。這通常是通過將對象字段設(shè)置為null或者從集合(collection)中移除對象而完成的。但是,注意,當(dāng)局部變量不再使用時,沒有必要將其顯式地設(shè)置為null。對這些變量的引用將隨著方法的退出而自動清除。

      概括地說,這就是內(nèi)存托管語言中的內(nèi)存泄漏產(chǎn)生的主要原因:保留下來卻永遠(yuǎn)不再使用的對象引用。

    典型泄漏

      既然我們知道了在Java中確實(shí)有可能發(fā)生內(nèi)存泄漏,就讓我們來看一些典型的內(nèi)存泄漏及其原因。

    全局集合

      在大的應(yīng)用程序中有某種全局的數(shù)據(jù)儲存庫是很常見的,例如一個JNDI樹或一個會話表。在這些情況下,必須注意管理儲存庫的大小。必須有某種機(jī)制從儲存庫中移除不再需要的數(shù)據(jù)。

      這可能有多種方法,但是最常見的一種是周期性運(yùn)行的某種清除任務(wù)。該任務(wù)將驗(yàn)證儲存庫中的數(shù)據(jù),并移除任何不再需要的數(shù)據(jù)。

      另一種管理儲存庫的方法是使用反向鏈接(referrer)計(jì)數(shù)。然后集合負(fù)責(zé)統(tǒng)計(jì)集合中每個入口的反向鏈接的數(shù)目。這要求反向鏈接告訴集合何時會退出入口。當(dāng)反向鏈接數(shù)目為零時,該元素就可以從集合中移除了。

    緩存

      緩存是一種數(shù)據(jù)結(jié)構(gòu),用于快速查找已經(jīng)執(zhí)行的操作的結(jié)果。因此,如果一個操作執(zhí)行起來很慢,對于常用的輸入數(shù)據(jù),就可以將操作的結(jié)果緩存,并在下次調(diào)用該操作時使用緩存的數(shù)據(jù)。

      緩存通常都是以動態(tài)方式實(shí)現(xiàn)的,其中新的結(jié)果是在執(zhí)行時添加到緩存中的。典型的算法是:

    檢查結(jié)果是否在緩存中,如果在,就返回結(jié)果。
    如果結(jié)果不在緩存中,就進(jìn)行計(jì)算。
    將計(jì)算出來的結(jié)果添加到緩存中,以便以后對該操作的調(diào)用可以使用。
      該算法的問題(或者說是潛在的內(nèi)存泄漏)出在最后一步。如果調(diào)用該操作時有相當(dāng)多的不同輸入,就將有相當(dāng)多的結(jié)果存儲在緩存中。很明顯這不是正確的方法。

      為了預(yù)防這種具有潛在破壞性的設(shè)計(jì),程序必須確保對于緩存所使用的內(nèi)存容量有一個上限。因此,更好的算法是:

    檢查結(jié)果是否在緩存中,如果在,就返回結(jié)果。
    如果結(jié)果不在緩存中,就進(jìn)行計(jì)算。
    如果緩存所占的空間過大,就移除緩存最久的結(jié)果。
    將計(jì)算出來的結(jié)果添加到緩存中,以便以后對該操作的調(diào)用可以使用。
      通過始終移除緩存最久的結(jié)果,我們實(shí)際上進(jìn)行了這樣的假設(shè):在將來,比起緩存最久的數(shù)據(jù),最近輸入的數(shù)據(jù)更有可能用到。這通常是一個不錯的假設(shè)。

      新算法將確保緩存的容量處于預(yù)定義的內(nèi)存范圍之內(nèi)。確切的范圍可能很難計(jì)算,因?yàn)榫彺嬷械膶ο笤诓粩嘧兓宜鼈兊囊冒_萬象。為緩存設(shè)置正確的大小是一項(xiàng)非常復(fù)雜的任務(wù),需要將所使用的內(nèi)存容量與檢索數(shù)據(jù)的速度加以平衡。

      解決這個問題的另一種方法是使用java.lang.ref.SoftReference類跟蹤緩存中的對象。這種方法保證這些引用能夠被移除,如果虛擬機(jī)的內(nèi)存用盡而需要更多堆的話。

    ClassLoader

      Java ClassLoader結(jié)構(gòu)的使用為內(nèi)存泄漏提供了許多可乘之機(jī)。正是該結(jié)構(gòu)本身的復(fù)雜性使ClassLoader在內(nèi)存泄漏方面存在如此多的問題。ClassLoader的特別之處在于它不僅涉及“常規(guī)”的對象引用,還涉及元對象引用,比如:字段、方法和類。這意味著只要有對字段、方法、類或ClassLoader的對象的引用,ClassLoader就會駐留在JVM中。因?yàn)镃lassLoader本身可以關(guān)聯(lián)許多類及其靜態(tài)字段,所以就有許多內(nèi)存被泄漏了。

    確定泄漏的位置

      通常發(fā)生內(nèi)存泄漏的第一個跡象是:在應(yīng)用程序中出現(xiàn)了OutOfMemoryError。這通常發(fā)生在您最不愿意它發(fā)生的生產(chǎn)環(huán)境中,此時幾乎不能進(jìn)行調(diào)試。有可能是因?yàn)闇y試環(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)行分析時,需要能夠斷開工具而保持系統(tǒng)不受干擾。

      雖然OutOfMemoryError通常都是內(nèi)存泄漏的信號,但是也有可能應(yīng)用程序確實(shí)正在使用這么多的內(nèi)存;對于后者,或者必須增加JVM可用的堆的數(shù)量,或者對應(yīng)用程序進(jìn)行某種更改,使它使用較少的內(nèi)存。但是,在許多情況下,OutOfMemoryError都是內(nèi)存泄漏的信號。一種查明方法是不間斷地監(jiān)控GC的活動,確定內(nèi)存使用量是否隨著時間增加。如果確實(shí)如此,就可能發(fā)生了內(nèi)存泄漏。

    詳細(xì)輸出

      有許多監(jiān)控垃圾收集器活動的方法。而其中使用最廣泛的可能是使用-Xverbose:gc選項(xiàng)啟動JVM,并觀察輸出。

    [memory ] 10.109-10.235: GC 65536K->16788K (65536K), 126.000 ms
     箭頭后面的值(本例中是16788K)是垃圾收集所使用的堆的容量。

    控制臺

      查看連續(xù)不斷的GC的詳細(xì)統(tǒng)計(jì)信息的輸出將是非常乏味的。幸好有這方面的工具。JRockit Management Console可以顯示堆使用量的圖示。借助于該圖,可以很容易地看出堆使用量是否隨時間增加。

    圖1. JRockit Management Console

      甚至可以配置該管理控制臺,以便如果發(fā)生堆使用量過大的情況(或基于其他的事件),控制臺能夠向您發(fā)送電子郵件。這明顯使內(nèi)存泄漏的查看變得更容易了。

    內(nèi)存泄漏檢測工具

      還有其他的專門進(jìn)行內(nèi)存泄漏檢測的工具。JRockit Memory Leak Detector可以用來查看內(nèi)存泄漏,并可以更深入地查出泄漏的根源。這個強(qiáng)大的工具是緊密集成到JRockit JVM中的,其開銷非常小,對虛擬機(jī)的堆的訪問也很容易。

    專業(yè)工具的優(yōu)點(diǎn)

      一旦知道確實(shí)發(fā)生了內(nèi)存泄漏,就需要更專業(yè)的工具來查明為什么會發(fā)生泄漏。JVM自己是不會告訴您的。這些專業(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ù)是指使用探測器處理字節(jié)碼以獲得工具所需的信息的技術(shù)。

      對于內(nèi)存泄漏檢測來說,這兩種技術(shù)有兩個缺點(diǎn),這使它們不太適合用于生產(chǎn)環(huán)境。首先,它們在內(nèi)存占用和性能降低方面的開銷不可忽略。有關(guān)堆使用量的信息必須以某種方式從JVM導(dǎo)出,并收集到工具中進(jìn)行處理。這意味著要為工具分配內(nèi)存。信息的導(dǎo)出也影響了JVM的性能。例如,當(dāng)收集信息時,垃圾收集器將運(yùn)行得比較慢。另外一個缺點(diǎn)是需要始終將工具連在JVM上。這是不可能的:將工具連在一個已經(jīng)啟動的JVM上,進(jìn)行分析,斷開工具,并保持JVM運(yùn)行。

      因?yàn)镴Rockit Memory Leak Detector是集成到JVM中的,就沒有這兩個缺點(diǎn)了。首先,許多處理和分析工作是在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)啟動的,Memory Leak Detector就可以與運(yùn)行中的JVM進(jìn)行連接或斷開。當(dāng)該工具斷開時,沒有任何東西遺留在JVM中,JVM又將以全速運(yùn)行代碼,正如工具連接之前一樣。

    趨勢分析

      讓我們深入地研究一下該工具以及它是如何用來跟蹤內(nèi)存泄漏的。在知道發(fā)生內(nèi)存泄漏之后,第一步是要弄清楚泄漏了什么數(shù)據(jù)--哪個類的對象引起了泄漏?JRockit Memory Leak Detector是通過在每次垃圾收集時計(jì)算每個類的現(xiàn)有對象的數(shù)目來實(shí)現(xiàn)這一步的。如果特定類的對象數(shù)目隨時間而增長(“增長率”),就可能發(fā)生了內(nèi)存泄漏。


    圖2. Memory Leak Detector的趨勢分析視圖

      因?yàn)樾孤┛赡芟窦?xì)流一樣非常小,所以趨勢分析必須運(yùn)行很長一段時間。在短時間內(nèi),可能會發(fā)生一些類的局部增長,而之后它們又會跌落。但是趨勢分析的開銷很小(最大開銷也不過是在每次垃圾收集時將數(shù)據(jù)包由JRockit發(fā)送到Memory Leak Detector)。開銷不應(yīng)該成為任何系統(tǒng)的問題——即使是一個全速運(yùn)行的生產(chǎn)中的系統(tǒng)。

      起初數(shù)目會跳躍不停,但是一段時間之后它們就會穩(wěn)定下來,并顯示出哪些類的數(shù)目在增長。

    找出根本原因

      有時候知道是哪些類的對象在泄漏就足以說明問題了。這些類可能只用于代碼中的非常有限的部分,對代碼進(jìn)行一次快速檢查就可以顯示出問題所在。遺憾地是,很有可能只有這類信息還并不夠。例如,常見到泄漏出在類java.lang.String的對象上,但是因?yàn)樽址谡麄€程序中都使用,所以這并沒有多大幫助。

      我們想知道的是,另外還有哪些對象與泄漏對象關(guān)聯(lián)?在本例中是String。為什么泄漏的對象還存在?哪些對象保留了對這些對象的引用?但是能列出的所有保留對String的引用的對象將會非常多,以至于沒有什么實(shí)際用處。為了限制數(shù)據(jù)的數(shù)量,可以將數(shù)據(jù)按類分組,以便可以看出其他哪些對象的類與泄漏對象(String)關(guān)聯(lián)。例如,String在Hashtable中是很常見的,因此我們可能會看到與String關(guān)聯(lián)的Hashtable數(shù)據(jù)項(xiàng)對象。由Hashtable數(shù)據(jù)項(xiàng)倒推,我們最終可以找到與這些數(shù)據(jù)項(xiàng)有關(guān)的Hashtable對象以及String(如圖3所示)。


    圖3. 在工具中看到的類型圖的示例視圖

    倒推

      因?yàn)槲覀內(nèi)匀皇且灶惖膶ο蠖皇菃为?dú)的對象來看待對象,所以我們不知道是哪個Hashtable在泄漏。如果我們可以弄清楚系統(tǒng)中所有的Hashtable都有多大,我們就可以假定最大的Hashtable就是正在泄漏的那一個(因?yàn)殡S著時間的流逝它會累積泄漏而增長得相當(dāng)大)。因此,一份有關(guān)所有Hashtable對象以及它們引用了多少數(shù)據(jù)的列表,將會幫助我們指出造成泄漏的確切Hashtabl。


    圖4. 界面:Hashtable對象以及它們所引用數(shù)據(jù)的數(shù)量的列表

      對對象引用數(shù)據(jù)數(shù)目的計(jì)算開銷非常大(需要以該對象作為根遍歷引用圖),如果必須對許多對象都這么做,將會花很多時間。如果了解一點(diǎn)Hashtable的內(nèi)部實(shí)現(xiàn)原理就可以找到一條捷徑。Hashtable的內(nèi)部有一個Hashtable數(shù)據(jù)項(xiàng)的數(shù)組。該數(shù)組隨著Hashtable中對象數(shù)目的增長而增長。因此,為找出最大的Hashtable,我們只需找出引用Hashtable數(shù)據(jù)項(xiàng)的最大數(shù)組。這樣要快很多。


    圖5. 界面:最大的Hashtable數(shù)據(jù)項(xiàng)數(shù)組及其大小的清單

    更進(jìn)一步

      當(dāng)找到發(fā)生泄漏的Hashtable實(shí)例時,我們可以看到其他哪些實(shí)例在引用該Hashtable,并倒推回去看看是哪個Hashtable在泄漏。


    圖 6. 這就是工具中的實(shí)例圖

      例如,該Hashtable可能是由MyServer類型的對象在名為activeSessions的字段中引用的。這種信息通常就足以查找源代碼以定位問題所在了。


    圖7. 檢查對象以及它對其他對象的引用

    找出分配位置

      當(dāng)跟蹤內(nèi)存泄漏問題時,查看對象分配到哪里是很有用的。只知道它們?nèi)绾闻c其他對象相關(guān)聯(lián)(即哪些對象引用了它們)是不夠的,關(guān)于它們在何處創(chuàng)建的信息也很有用。當(dāng)然了,您并不想創(chuàng)建應(yīng)用程序的輔助構(gòu)件,以打印每次分配的堆棧跟蹤(stack trace)。您也不想僅僅為了跟蹤內(nèi)存泄漏而在運(yùn)行應(yīng)用程序時將一個分析程序連接到生產(chǎn)環(huán)境中。

      借助于JRockit Memory Leak Detector,應(yīng)用程序中的代碼可以在分配時進(jìn)行動態(tài)添加,以創(chuàng)建堆棧跟蹤。這些堆棧跟蹤可以在工具中進(jìn)行累積和分析。只要不啟用就不會因該功能而產(chǎn)生成本,這意味著隨時可以進(jìn)行分配跟蹤。當(dāng)請求分配跟蹤時,JRockit 編譯器動態(tài)插入代碼以監(jiān)控分配,但是只針對所請求的特定類。更好的是,在進(jìn)行數(shù)據(jù)分析時,添加的代碼全部被移除,代碼中沒有留下任何會引起應(yīng)用程序性能降低的更改。


    圖8. 示例程序執(zhí)行期間String的分配的堆棧跟蹤

    結(jié)束語

      內(nèi)存泄漏是難以發(fā)現(xiàn)的。本文重點(diǎn)介紹了幾種避免內(nèi)存泄漏的最佳實(shí)踐,包括要始終記住在數(shù)據(jù)結(jié)構(gòu)中所放置的內(nèi)容,以及密切監(jiān)控內(nèi)存使用量以發(fā)現(xiàn)突然的增長。

      我們都已經(jīng)看到了JRockit Memory Leak Detector是如何用于生產(chǎn)中的系統(tǒng)以跟蹤內(nèi)存泄漏的。該工具使用一種三步式的方法來找出泄漏。首先,進(jìn)行趨勢分析,找出是哪個類的對象在泄漏。接下來,看看有哪些其他的類與泄漏的類的對象相關(guān)聯(lián)。最后,進(jìn)一步研究單個對象,看看它們是如何互相關(guān)聯(lián)的。也有可能對系統(tǒng)中所有對象分配進(jìn)行動態(tài)的堆棧跟蹤。這些功能以及該工具緊密集成到JVM中的特性使您可以以一種安全而強(qiáng)大的方式跟蹤內(nèi)存泄漏并進(jìn)行修復(fù)。

    參考資料

    JRockit工具下載
    BEA JRockit 5.0說明文檔
    JRockit 5.0中的新功能和新工具
    BEA JRockit DevCenter

    主站蜘蛛池模板: 国产aⅴ无码专区亚洲av| 黄网站免费在线观看| 中文字幕亚洲第一在线| 亚洲国产小视频精品久久久三级| 四虎在线视频免费观看视频| 色www永久免费| 福利片免费一区二区三区| 亚洲色欲色欲www| 91亚洲精品视频| 亚洲成色WWW久久网站| 久久久精品国产亚洲成人满18免费网站| 永久在线毛片免费观看| 免费成人福利视频| 免费无码成人AV在线播放不卡| 国产日韩精品无码区免费专区国产 | 亚洲日本一区二区三区在线| 免费a级毛片大学生免费观看| 啦啦啦高清视频在线观看免费| 4444www免费看| 污视频在线免费观看| 你是我的城池营垒免费观看完整版 | 国产黄片不卡免费| 午夜免费国产体验区免费的| 亚洲乱色熟女一区二区三区蜜臀| 亚洲人成网站看在线播放| 亚洲国产精品久久网午夜| 亚洲白色白色在线播放| 亚洲网红精品大秀在线观看 | 最近最新高清免费中文字幕 | 亚洲AV无码乱码在线观看裸奔| 亚洲精品无码久久久久去q | 久久精品成人免费看| 国精产品一区一区三区免费视频| 99精品免费视品| a视频在线免费观看| 成在人线av无码免费高潮喷水 | 亚洲激情在线视频| 亚洲综合久久1区2区3区| 亚洲熟妇无码久久精品| 亚洲AV综合色区无码二区偷拍 | 久久久久久99av无码免费网站|