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

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

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

    心的方向

    新的征途......
    posts - 75,comments - 19,trackbacks - 0

    Java系統(tǒng)中內(nèi)存泄漏測試方法的研究

    本文鏈接:http://user.qzone.qq.com/18485108/blog/1203413108
    摘 要 穩(wěn)定性是衡量軟件系統(tǒng)質(zhì)量的重要指標(biāo),內(nèi)存泄漏是破壞系統(tǒng)穩(wěn)定性的重要因素。由于采用垃圾回收機制,Java語言的內(nèi)存泄漏的模式與C++等語言相比有很大的不同。全文通過與C++中的內(nèi)存泄漏問題進行對比,講述了Java內(nèi)存泄漏的基本原理,以及如何借助Optimizeit profiler工具來測試內(nèi)存泄漏和分析內(nèi)存泄漏的原因,在實踐中證明這是一套行之有效的方法。

      關(guān)鍵詞 Java; 內(nèi)存泄漏; GC(垃圾收集器) 引用; Optimizeit

      問題的提出

      筆者曾經(jīng)參與開發(fā)的網(wǎng)管系統(tǒng),系統(tǒng)規(guī)模龐大,涉及上百萬行代碼。系統(tǒng)主要采用Java語言開發(fā),大體上分為客戶端、服務(wù)器和數(shù)據(jù)庫三個層次。在版本進入測試和試用的過程中,現(xiàn)場人員和測試部人員紛紛反映:系統(tǒng)的穩(wěn)定性比較差,經(jīng)常會出現(xiàn)服務(wù)器端運行一晝夜就死機的現(xiàn)象,客戶端跑死的現(xiàn)象也比較頻繁地發(fā)生。對于網(wǎng)管系統(tǒng)來講,經(jīng)常性的服務(wù)器死機是個比較嚴(yán)重的問題,因為頻繁的死機不僅可能導(dǎo)致前后臺數(shù)據(jù)不一致,發(fā)生錯誤,更會引起用戶的不滿,降低客戶的信任度。因此,服務(wù)器端的穩(wěn)定性問題必須盡快解決。

      解決思路

      通過察看服務(wù)器端日志,發(fā)現(xiàn)死機前服務(wù)器端頻繁拋出OutOfMemoryException內(nèi)存溢出錯誤,因此初步把死機的原因定位為內(nèi)存泄漏引起內(nèi)存不足,進而引起內(nèi)存溢出錯誤。如何查找引起內(nèi)存泄漏的原因呢?有兩種思路:第一種,安排有經(jīng)驗的編程人員對代碼進行走查和分析,找出內(nèi)存泄漏發(fā)生的位置;第二種,使用專門的內(nèi)存泄漏測試工具Optimizeit進行測試。這兩種方法都是解決系統(tǒng)穩(wěn)定性問題的有效手段,使用內(nèi)存測試工具對于已經(jīng)暴露出來的內(nèi)存泄漏問題的定位和解決非常有效;但是軟件測試的理論也告訴我們,系統(tǒng)中永遠(yuǎn)存在一些沒有暴露出來的問題,而且,系統(tǒng)的穩(wěn)定性問題也不僅僅只是內(nèi)存泄漏的問題,代碼走查是提高系統(tǒng)的整體代碼質(zhì)量乃至解決潛在問題的有效手段。基于這樣的考慮,我們的內(nèi)存穩(wěn)定性工作決定采用代碼走查結(jié)合測試工具的使用,雙管齊下,爭取比較徹底地解決系統(tǒng)的穩(wěn)定性問題。

      在代碼走查的工作中,安排了對系統(tǒng)業(yè)務(wù)和開發(fā)語言工具比較熟悉的開發(fā)人員對應(yīng)用的代碼進行了交叉走查,找出代碼中存在的數(shù)據(jù)庫連接聲明和結(jié)果集未關(guān)閉、代碼冗余和低效等故障若干,取得了良好的效果,文中主要講述結(jié)合工具的使用對已經(jīng)出現(xiàn)的內(nèi)存泄漏問題的定位方法。

      內(nèi)存泄漏的基本原理

      在C++語言程序中,使用new操作符創(chuàng)建的對象,在使用完畢后應(yīng)該通過delete操作符顯示地釋放,否則,這些對象將占用堆空間,永遠(yuǎn)沒有辦法得到回收,從而引起內(nèi)存空間的泄漏。如下的簡單代碼就可以引起內(nèi)存的泄漏:

    void function(){
     Int[] vec = new int[5];
    }
      在function()方法執(zhí)行完畢后,vec數(shù)組已經(jīng)是不可達(dá)對象,在C++語言中,這樣的對象永遠(yuǎn)也得不到釋放,稱這種現(xiàn)象為內(nèi)存泄漏。

      而Java是通過垃圾收集器(Garbage Collection,GC)自動管理內(nèi)存的回收,程序員不需要通過調(diào)用函數(shù)來釋放內(nèi)存,但它只能回收無用并且不再被其它對象引用的那些對象所占用的空間。在下面的代碼中,循環(huán)申請Object對象,并將所申請的對象放入一個Vector中,如果僅僅釋放對象本身,但是因為Vector仍然引用該對象,所以這個對象對GC來說是不可回收的。因此,如果對象加入到Vector后,還必須從Vector中刪除,最簡單的方法就是將Vector對象設(shè)置為null。

    Vector v = new Vector(10);
    for (int i = 1; i < 100; i++)
    {
     Object o = new Object();
     v.add(o);
     o = null;
    }//此時,所有的Object對象都沒有被釋放,因為變量v引用這些對象。
      實際上無用,而還被引用的對象,GC就無能為力了(事實上GC認(rèn)為它還有用),這一點是導(dǎo)致內(nèi)存泄漏最重要的原因。

      Java的內(nèi)存回收機制可以形象地理解為在堆空間中引入了重力場,已經(jīng)加載的類的靜態(tài)變量和處于活動線程的堆棧空間的變量是這個空間的牽引對象。這里牽引對象是指按照J(rèn)ava語言規(guī)范,即便沒有其它對象保持對它的引用也不能夠被回收的對象,即Java內(nèi)存空間中的本原對象。當(dāng)然類可能被去加載,活動線程的堆棧也是不斷變化的,牽引對象的集合也是不斷變化的。對于堆空間中的任何一個對象,如果存在一條或者多條從某個或者某幾個牽引對象到該對象的引用鏈,則就是可達(dá)對象,可以形象地理解為從牽引對象伸出的引用鏈將其拉住,避免掉到回收池中;而其它的不可達(dá)對象由于不存在牽引對象的拉力,在重力的作用下將掉入回收池。在圖1中,A、B、C、D、E、F六個對象都被牽引對象所直接或者間接地“牽引”,使得它們避免在重力的作用下掉入回收池。如果TR1-A鏈和TR2-D鏈斷開,則A、B、C三個對象由于失去牽引,在重力的作用下掉入回收池(被回收),D對象也是同樣的原因掉入回收池,而F對象仍然存在一個牽引鏈(TR3-E-F),所以不會被回收,如圖2、3所示。

      
      圖1 初始狀態(tài)

      
      圖2 TR1-A鏈和TR2-D鏈斷開,A、B、C、D掉入回收池

      
      圖3 A、B、C、D四個對象被回收

      通過前面的介紹可以看到,由于采用了垃圾回收機制,任何不可達(dá)對象都可以由垃圾收集線程回收。因此通常說的Java內(nèi)存泄漏其實是指無意識的、非故意的對象引用,或者無意識的對象保持。無意識的對象引用是指代碼的開發(fā)人員本來已經(jīng)對對象使用完畢,卻因為編碼的錯誤而意外地保存了對該對象的引用(這個引用的存在并不是編碼人員的主觀意愿),從而使得該對象一直無法被垃圾回收器回收掉,這種本來以為可以釋放掉的卻最終未能被釋放的空間可以認(rèn)為是被“泄漏了”。

      這里通過一個例子來演示Java的內(nèi)存泄漏。假設(shè)有一個日志類Logger,其提供一個靜態(tài)的log(String msg)方法,任何其它類都可以調(diào)用Logger.Log(message)來將message的內(nèi)容記錄到系統(tǒng)的日志文件中。Logger類有一個類型為HashMap的靜態(tài)變量temp,每次在執(zhí)行l(wèi)og(message)方法的時候,都首先將message的值丟入temp中(以當(dāng)前線程+當(dāng)前時間為鍵),在方法退出之前再從temp中將以當(dāng)前線程和當(dāng)前時間為鍵的條目刪除。注意,這里當(dāng)前時間是不斷變化的,所以log方法在退出之前執(zhí)行刪除條目的操作并不能刪除方法執(zhí)行之初丟入的條目。這樣,任何一個作為參數(shù)傳給log方法的字符串最終由于被Logger的靜態(tài)變量temp引用,而無法得到回收,這種違背實現(xiàn)者主觀意圖的無意識的對象保持就是我們所說的Java內(nèi)存泄漏。
    鑒別泄漏對象的方法

      一般說來,一個正常的系統(tǒng)在其運行穩(wěn)定后其內(nèi)存的占用量是基本穩(wěn)定的,不應(yīng)該是無限制的增長的,同樣,對任何一個類的對象的使用個數(shù)也有一個相對穩(wěn)定的上限,不應(yīng)該是持續(xù)增長的。根據(jù)這樣的基本假設(shè),我們可以持續(xù)地觀察系統(tǒng)運行時使用的內(nèi)存的大小和各實例的個數(shù),如果內(nèi)存的大小持續(xù)地增長,則說明系統(tǒng)存在內(nèi)存泄漏,如果某個類的實例的個數(shù)持續(xù)地增長,則說明這個類的實例可能存在泄漏情況。

      Optimizeit是Borland公司的產(chǎn)品,主要用于協(xié)助對軟件系統(tǒng)進行代碼優(yōu)化和故障診斷,其功能眾多,使用方便,其中的OptimizeIt Profiler主要用于內(nèi)存泄漏的分析。Profiler的堆視圖(如圖4)就是用來觀察系統(tǒng)運行使用的內(nèi)存大小和各個類的實例分配的個數(shù)的,其界面如圖四所示,各列自左至右分別為類名稱、當(dāng)前實例個數(shù)、自上個標(biāo)記點開始增長的實例個數(shù)、占用的內(nèi)存空間的大小、自上次標(biāo)記點開始增長的內(nèi)存的大小、被釋放的實例的個數(shù)信息、自上次標(biāo)記點開始增長的內(nèi)存的大小被釋放的實例的個數(shù)信息,表的最后一行是匯總數(shù)據(jù),分別表示目前JVM中的對象實例總數(shù)、實例增長總數(shù)、內(nèi)存使用總數(shù)、內(nèi)存使用增長總數(shù)等。

      在實踐中,可以分別在系統(tǒng)運行四個小時、八個小時、十二個小時和二十四個小時時間點記錄當(dāng)時的內(nèi)存狀態(tài)(即抓取當(dāng)時的內(nèi)存快照,是工具提供的功能,這個快照也是供下一步分析使用),找出實例個數(shù)增長的前十位的類,記錄下這十個類的名稱和當(dāng)前實例的個數(shù)。在記錄完數(shù)據(jù)后,點擊Profiler中右上角的Mark按鈕,將該點的狀態(tài)作為下一次記錄數(shù)據(jù)時的比較點。

      
      圖4 Profiler 堆視圖

      系統(tǒng)運行二十四小時以后可以得到四個內(nèi)存快照。對這四個內(nèi)存快照進行綜合分析,如果每一次快照的內(nèi)存使用都比上一次有增長,可以認(rèn)定系統(tǒng)存在內(nèi)存泄漏,找出在四個快照中實例個數(shù)都保持增長的類,這些類可以初步被認(rèn)定為存在泄漏。

      分析與定位

      通過上面的數(shù)據(jù)收集和初步分析,可以得出初步結(jié)論:系統(tǒng)是否存在內(nèi)存泄漏和哪些對象存在泄漏(被泄漏),如果結(jié)論是存在泄漏,就可以進入分析和定位階段了。

      前面已經(jīng)談到Java中的內(nèi)存泄漏就是無意識的對象保持,簡單地講就是因為編碼的錯誤導(dǎo)致了一條本來不應(yīng)該存在的引用鏈的存在(從而導(dǎo)致了被引用的對象無法釋放),因此內(nèi)存泄漏分析的任務(wù)就是找出這條多余的引用鏈,并找到其形成的原因。前面還講到過牽引對象,包括已經(jīng)加載的類的靜態(tài)變量和處于活動線程的堆棧空間的變量。由于活動線程的堆棧空間是迅速變化的,處于堆棧空間內(nèi)的牽引對象集合是迅速變化的,而作為類的靜態(tài)變量的牽引對象的集合在系統(tǒng)運行期間是相對穩(wěn)定的。

      對每個被泄漏的實例對象,必然存在一條從某個牽引對象出發(fā)到達(dá)該對象的引用鏈。處于堆棧空間的牽引對象在被從棧中彈出后就失去其牽引的能力,變?yōu)榉菭恳龑ο螅虼耍陂L時間的運行后,被泄露的對象基本上都是被作為類的靜態(tài)變量的牽引對象牽引。

      Profiler的內(nèi)存視圖除了堆視圖以外,還包括實例分配視圖(圖5)和實例引用圖(圖6)。

      Profiler的實例引用圖為找出從牽引對象到泄漏對象的引用鏈提供了非常直接的方法,其界面的第二個欄目中顯示的就是從泄漏對象出發(fā)的逆向引用鏈。需要注意的是,當(dāng)一個類的實例存在泄漏時,并非其所有的實例都是被泄漏的,往往只有一部分是被泄漏對象,其它則是正常使用的對象,要判斷哪些是正常的引用鏈,哪些是不正常的引用鏈(引起泄漏的引用鏈)。通過抽取多個實例進行引用圖的分析統(tǒng)計以后,可以找出一條或者多條從牽引對象出發(fā)的引用鏈,下面的任務(wù)就是找出這條引用鏈形成的原因。

      實例分配圖提供的功能是對每個類的實例的分配位置進行統(tǒng)計,查看實例分配的統(tǒng)計結(jié)果對于分析引用鏈的形成具有一定的作用,因為找到分配鏈與引用鏈的交點往往就可以找到了引用鏈形成的原因,下面將具體介紹。

      
      圖5 實例分配圖

      
      圖6 實例引用圖

      設(shè)想一個實例對象a在方法f中被分配,最終被實例對象b所引用,下面來分析從b到a的引用鏈可能的形成原因。方法f在創(chuàng)建對象a后,對它的使用分為四種情況:1、將a作為返回值返回;2、將a作為參數(shù)調(diào)用其它方法;3、在方法內(nèi)部將a的引用傳遞給其它對象;4、其它情況。其中情況4不會造成由b到a的引用鏈的生成,不用考慮。下面考慮其它三種情況:對于1、2兩種情況,其造成的結(jié)果都是在另一個方法內(nèi)部獲得了對象a的引用,它的分析與方法f的分析完全一樣(遞歸分析);考慮第3種情況:1、假設(shè)方法f直接將對象a的引用加入到對象b,則對象b到a的引用鏈就找到了,分析結(jié)束;2、假設(shè)方法f將對象a的引用加入到對象c,則接下來就需要跟蹤對象c的使用,對象c的分析比對象a的分析步驟更多一些,但大體原理都是一樣的,就是跟蹤對象從創(chuàng)建后被使用的歷程,最終找到其被牽引對象引用的原因。

      現(xiàn)在將泄漏對象的引用鏈以及引用鏈形成的原因找到了,內(nèi)存泄漏測試與分析的工作就到此結(jié)束,接下來的工作就是修改相應(yīng)的設(shè)計或者實現(xiàn)中的錯誤了。

      總結(jié)

      使用上述的測試和分析方法,在實踐中先后進行了三次測試,找出了好幾處內(nèi)存泄漏錯誤。系統(tǒng)的穩(wěn)定性得到很大程度的提高,最初運行1~2天就拋出內(nèi)存溢出異常,修改完成后,系統(tǒng)從未出現(xiàn)過內(nèi)存溢出異常。此方法適用于任何使用Java語言開發(fā)的、對穩(wěn)定性有比較高要求的軟件系統(tǒng)。
    posted on 2008-02-24 18:23 阿偉 閱讀(305) 評論(0)  編輯  收藏 所屬分類: J2EE
    主站蜘蛛池模板: 国产精品免费大片| 成人毛片免费观看视频大全| 国产成人免费片在线视频观看| 免费在线观看毛片| 国产AV旡码专区亚洲AV苍井空| 亚洲黄色中文字幕| 一级毛片**不卡免费播| 亚洲国产天堂在线观看| 亚洲AV无码一区二区乱子仑| 中文字幕免费在线视频| 国产精一品亚洲二区在线播放| 亚洲色偷偷综合亚洲AV伊人蜜桃| 本免费AV无码专区一区| 国产成人亚洲综合色影视| 午夜精品免费在线观看| 亚洲国产高清美女在线观看 | 在线免费观看一级毛片| 亚洲精品免费观看| 91黑丝国产线观看免费| 亚洲 欧洲 自拍 另类 校园| 久久午夜无码免费| 亚洲一区二区三区免费观看 | 狠狠色伊人亚洲综合成人| 免费精品无码AV片在线观看| 免费a在线观看播放| 亚洲av永久无码嘿嘿嘿| 日本免费一区二区三区最新| 久久av无码专区亚洲av桃花岛| www成人免费视频| 免费看香港一级毛片| 成在线人视频免费视频| 成人永久免费高清| 中国一级特黄的片子免费 | 亚洲国产精品无码av| 日韩国产免费一区二区三区| 亚洲成aⅴ人片久青草影院按摩| 999国内精品永久免费观看| 国产91成人精品亚洲精品| 成年女人免费碰碰视频| 性生大片视频免费观看一级| 亚洲综合色丁香麻豆|