Java EE應用中的性能問題解決方案 — 第一部分 內存溢出和JVM內存管理內幕(B)

聲明:本文禁止未經本人同意的任何形式轉載!如有轉載需求,可與本人通過個人資料中的電子郵箱聯繫。對於未經同意的轉載,本人將保留進一步行動的權利!

IBM的JVM內存管理 IBM的JVM稍有不同。與其從一個大的分片堆開始,它將所有的對象保留在單一空間中並在堆增長時釋放內存。它通過運行不同層級的垃圾回收實現。主要的表現是:堆開始時相對較小,被填滿,並在某時間點上執行簡潔型的標記清掃垃圾回收來清除生命週期完畢的對象並在堆的末端對活動對象進行壓縮。當堆逐步增長時,長期活動的對象被不斷推向堆的末端。所以斷定潛在的內存泄露的最好途徑是觀察堆的完整動作:堆有不斷向上增長的趨勢麼?

解決內存泄露的問題 內存泄露問題比較複雜,但是如果能診斷出導致內存泄露的請求,那麼問題就簡單一些。將應用放到開發環境中,並在內存分析器模式下運行,並執行以下步驟: 1、 在內存分析器中啓動應用 2、 執行用例(發出請求) 3、 記錄堆情況,在再次執行用例前獲得堆中的所有對象的信息 4、 再次執行用例 5、 再次記錄堆情況 6、 對比兩次堆的情況,尋找在用例執行完成後堆中本不應該出現的對象

現在,就可以結合源代碼來查看是否有對象產生了內存泄露,或是有意識地將這些對象保留在內存中的行爲。

如果前面6步完成後並沒有明確內存泄露的問題,有一個辦法就是將第4步執行n遍,希望在n遍後的內存中出現n個或者n的倍數個的一些內容。雖然這個辦法並非總能奏效,但卻能最大程度幫助尋找內存泄露的對象。

如果不能將內存泄露從一個特定的請求中分離出來,可以有以下的選擇: • 分析可疑的請求直至找出內存泄露 • 配置有內存功能的監控工具

第一種方法對小型應用還尚可行,但對於大型應用來說就不適用了。第二種方案更高效如果能夠訪問到監控工具的話。這些工具使用字節碼工具追蹤對象創建和銷燬的計數,並通常報告預定義的和用戶自定義的類的對象個數,例如集合框架的類。例如,監控工具可能會報告/action/login.do請求完成後留下了100個HashMap的對象。雖然這樣並沒有明確內存泄露發生的地方,但至少已經通過較小的額外開銷了解到需要到內存分析器中仔細查看哪些請求。在生產環境下不能宕機而需要找出內存泄露的地方是很苛刻的,所以就需要使用這些工具來降低一下工作的複雜度。

所有這些背景資料都爲我們判斷內存泄露提供了資料。在Java中,內存泄露是一個對象保留了對另一個並不需要的對象的引用,因此阻止了垃圾收集對空間的回收。在Sun的JVM架構中,未被解除引用的對象將從Eden和兩個存活空間移至老生代。試想,如果在多用戶的環境下,多個請求執行了內存泄露的代碼,我們就能觀察到老生代的增長。

下圖展現了可能導致內存泄露的對象:灰色的,逃過了幾次大規模收集的老生代對象。並非老生代中的所有對象都是內存泄露的對象,但發生了內存泄露的對象最終會出現在老生代中。如果內存泄露真實存在,老生代就會被內存泄露的對象佔據,直至內存溢出。

 

因此,我們必須設法追蹤老生代的垃圾回收效率:每當大規模垃圾回收發生時,能回收多少內存?老生代中的內存是否是按照可識別的模式增長?
 
可以通過監控API來獲得一些信息,當然細節的信息要通過詳細垃圾收集日誌來獲取。日誌記錄的級別可能會影響JVM的性能(記錄越詳細,性能損失越大)。爲了判斷是否存在內存泄露,可以採用相對比較標準的設置來查看各個不同的“代”(分片)中的內存在垃圾回收之間的情況來做出相應的判斷。Sun的文檔中顯示這種記錄級別可能會帶來5%的額外開銷:
 
 
觀察堆的總體趨勢能發現可能的內存泄露,但是明確觀察老生代的增長率纔是更加權威的判斷。
聲明:本文禁止未經本人同意的任何形式轉載!如有轉載需求,可與本人通過個人資料中的電子郵箱聯繫。對於未經同意的轉載,本人將保留進一步行動的權利!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章