記憶體流失是比較常見的一種應用程式效能問題,一旦發生,則系統的可用記憶體和效能持續下降;最終將導致記憶體不足(OutOfMemory),系統徹底宕掉,不能響應任何請求,其危害相當嚴重。同時,Java堆(Heap)中大量的對象以及對象間之複雜關係,導致記憶體流失問題的探測和分析均比較困難,採用相應的協助工具輔助是很必要的。
我使用的比較多的是Memory Dump Diagnostic for Java (MDD4J)和IBM HeapAnalyzer,這兩個工具都能支援幾乎所有JDK版本所產生的堆轉儲檔案,使用前可以在兩者的協助檔案中查看一下支援列表。
先說一下IBM HeapAnalyzer,{
function onclick()
{
pageTracker._trackPageview('/outbound/article/www.alphaworks.ibm.com');
}
}" href="http://www.alphaworks.ibm.com/tech/heapanalyzer" target="_blank">下載之後首先閱讀一下readme,這上面詳細寫了HeapAnalyzer的使用方法。對於我用的2.6版本(最新為3.8),可以在命令列中輸入<Java path>java –Xmx[heapsize] –jar ha26.jar <heapdump file>來啟動工具並載入heapdump檔案。對於比較大的heapdump,將-Xmx設定一個較大的值(大於heapdump的大小),來避免載入過程中的OOM。對於64位機器上產生的超大heapdump,個人機器上分析就不大可能了。
開啟heapdump檔案後,我一般點擊“Analysis”裡的“Tree View”,以樹的形式從根節點展示記憶體對象分配的資訊
第一行java.lang.ref.Refenrence這個class及它的76個children佔用了67%的已用堆大小(31M/46M),它本身僅佔用了76bits。雙擊java.lang.ref.Refenrence,我們可以看到它所引用的兩個子節點。其中一個子節點java.lang.ref.Finalizer後的67%指引我們記憶體流失的問題應該在它的引用上。
接下去你可以逐級展開,或者右鍵點擊“Locate a leak suspect”,讓HeapAnalyzer幫你找到泄漏可能發生的地方。泄漏一般發生在那些擁有“超乎尋常多”的引用(子節點)的class上,正是這些建立後沒有釋放、累積了成千上百的對象,造成了OutOfMemory。右鍵中的“Go to the largest drop subtrees”也是以此為原理而設的,它的解釋為:
“Search for total size drop” will find a size drop between the total size of a parent and the biggest total size of child of the parent.
因為出現泄漏的點,每個子節點佔用的記憶體空間不大,但是巨大的數量會導致父節點佔用的total size很大。不過反過來尋找到的點都是泄漏發生的地方這種說法是不成立的,否則也不需要我們來分析了。
更多細節的內容,可以看這篇{
function onclick()
{
pageTracker._trackPageview('/outbound/article/docs.google.com');
}
}" href="http://docs.google.com/fileview?id=F.242a8f41-b76f-47ba-8350-abeaff1d0d68" target="_blank">PPT
Memory Dump Diagnostic for Java (MDD4J)則是IBM Support Assistant(ISA)裡的一個工具,可以在ISA裡載入。它的使用方法和HeapAnalyzer類似,不過它會自動列出“可疑泄漏點”供分析。所依據的,是“分析演算法尋找父物件與子物件之間對象大小的顯著變化。這些發生顯著變化的父物件可能是基於數組的容器物件,它們包含大量不斷增大的子物件。”
具體的使用方法可以參考{
function onclick()
{
pageTracker._trackPageview('/outbound/article/www.ibm.com');
}
}" href="http://www.ibm.com/developerworks/cn/websphere/library/techarticles/0608_poddar/0608_poddar.html" target="_blank">《WebSphere Application Server 中的記憶體流失檢測與分析:第 2 部分:用於泄漏檢測與分析的工具和功能》一文中的實際案例。(不過文中的版本應該比較低,現在能下到的2和3版本有些不同,不過不妨礙使用).
Heapdump工具的使用很簡單,痛點在於找到“記憶體流失的真正原因”,一般需要通過多個heapdump檔案的對比才能找到。
比較分析用於對運行記憶體流失應用程式期間(即可用 Java 堆記憶體流失時)擷取的兩個記憶體轉儲進行分析。在運行泄漏應用程式的早期觸發的記憶體轉儲被稱為基準記憶體轉儲,發生泄漏的應用程式運行一段時間(以允許泄漏程度加大)後觸發的記憶體轉儲被稱為主記憶體轉儲。在發生了記憶體流失的情況下,主記憶體轉儲可能包含大量對象,而這些對象佔用的 Java 堆空間量會比基準記憶體轉儲大很多。
為了獲得更好的分析結果,建議使主記憶體轉儲的觸發點與基準記憶體轉儲的觸發點在時間上拉開一定距離,從而使總耗用堆大小在兩個觸發點之間大幅增長。
如果發現“主記憶體轉儲”中的某個對象數量大大大於“基準記憶體轉儲”,那麼這個對象一般就是發生泄漏的點。但是要避免在appserver剛啟動時就做heapdump,否則會把正常需要分配的對象當作泄漏嫌疑點。比如原先運行3天會發生OOM,那麼可以:縮小堆大小,讓OOM提早發生;在運行4個小時後每隔4小時手動做一次Heapdump直到OOM發生。這些動作也許不適合在生產環境下進行,可以另建測試環境進行。
之前幾篇文章中介紹的分析gc log,和本文講到的分析heapdump,都是離線分析法。它們的缺陷就是無法找到代碼引起的“效能低下”的原因,正如《用HPjtune分析GC日誌》裡所看到的那樣,系統效能很差,但是沒有OOM發生,可用堆在每次full gc後還不斷減少的現象不能簡單怪罪為記憶體流失,畢竟最後都回收下來了,如果手動做heapdump,可能有問題的對象已被回收,無法得到正確的結果。這種情況下要使用諸如Jprofile這樣直接附加到JVM上的工具來監測了。
最後附一下手動產生heapdump的方法,免得事到臨頭在google。
在Linux/AIX環境下
使用Kill -3 pid命令來調用堆轉儲.
Windows環境下
1. 找到JVM對象名字。
<wsadmin> set objectName [$AdminControl queryNamesWebSphere:type=JVM,process=<servername>,node=<nodename>,*]
2. 對JVM MBean調用generateHeapDump操作。
<wsadmin> $AdminControl invoke $objectName generateHeapDump
如果上述方法是沒有產生,那麼進行下面的設定。
訪問管理主控台
轉到“伺服器”>“應用程式伺服器”> Server1(或者要擷取其堆轉儲的伺服器的名稱)>“進程定義”>“環境條目”。
單擊“建立”。
在“名稱”欄位中,輸入 IBM_HEAPDUMP(預設是開啟的)。在“值”欄位中,輸入 true。
單擊“確定”。
重複步驟 3 至 5,但將 IBM_HEAPDUMP_OUTOFMEMORY 設定為 true。
預設情況下,將在 ~/WebSphere/AppServer/ 目錄中建立記憶體轉儲(對於 WebSphere Application Server V6.x 而言,預設目錄是:~/WebSphere/AppServer/profiles/default)。要將堆轉儲目標定向到另一個目錄,請轉至“環境條目”,單擊“建立”,將 IBM_HEAPDUMPDIR 設定為適當的目錄(例如 /heapdumps),然後單擊“確定”。
單擊“儲存”,然後在下一個螢幕中再次單擊“儲存”。
轉到“伺服器”>“應用程式伺服器”> server1(或者要擷取其堆轉儲的伺服器的名稱)>“進程定義”>“JAVA 虛擬機器”。
選擇“詳細記憶體回收”。
單擊“儲存”,然後在下一個螢幕中再次單擊“儲存”。
重新啟動伺服器。
開啟命令提示字元並轉至 /WebSphere/AppServer/bin 目錄。
通過發出 kill -3 XXXXX 命令來調用堆轉儲,其中 XXXXX 是進程標識。
如果WebSphere運行在HP-UX上,那麼需要
- 訪問管理主控台
- 轉到“伺服器”>“應用程式伺服器”> Server1(或者要擷取其堆轉儲的伺服器的名稱)>“進程定義”>“環境條目”。
- 在“常規參數”中,輸入:-Xrunhprof:depth=0,heap=dump,format=a,thread=n,doe=n
- 預設情況下,將在 ~/Websphere/AppServer/ 目錄中建立記憶體轉儲。要將堆轉儲目標定向到另一個目錄,請添加 HProf 參數 file=/heapdumpdir/hprof.txt,其中 heapdumpdir 是適當的目錄,而 hprof.txt 是適當的檔案名稱。如果建立了多個記憶體轉儲,那麼將把每個記憶體轉儲追加到同一個 hprof.txt 檔案中。
- 選中“啟用詳細記憶體回收方式”。
- 重新啟動伺服器。
- 通過發出 kill -3 XXXXX 命令建立堆轉儲,其中 XXXXX 是進程標識。
- 除非另有指定,否則將在 ~/WebSphere/AppServer/ 目錄中建立 hprof 轉儲,並且檔案名稱看起來類似於 java.hprof.txt。
- 關閉應用程式伺服器,然後移動 hprof 轉儲檔案。直到正確關閉應用程式伺服器之後,hprof 轉儲檔案才完整。
- 注意:請檢查是否每個 hprof 轉儲都包含 HEAP DUMP BEGIN 和 HEAP DUMP END 這兩組標記。如果 hprof 轉儲的這兩組標記不齊全,那麼表明該轉儲不完整且不能用於分析。
備忘:
本文轉載自:http://www.hashei.me/2009/07/heapanalyzer-and-mod4j-introduction.html