1. 程式人生 > >使用 Eclipse Memory Analyzer 進行記憶體洩漏分析的一次過程

使用 Eclipse Memory Analyzer 進行記憶體洩漏分析的一次過程

在平時開發、測試過程中、甚至是生產環境中,有時會遇到OutOfMemoryError,Java堆溢位了,這表明程式有嚴重的問題。我們需要找造成OutOfMemoryError原因。一般有兩種情況:

1、記憶體洩露,物件已經死了,無法通過垃圾收集器進行自動回收,通過找出洩露的程式碼位置和原因,才好確定解決方案;
2、記憶體溢位,記憶體中的物件都還必須存活著,這說明Java堆分配空間不足,檢查堆設定大小(-Xmx與-Xms),檢查程式碼是否存在物件生命週期太長、持有狀態時間過長的情況。
以上是處理Java堆問題的思路,具體是怎麼進行分析,這裡介紹的是使用Eclipse Memory Analyzer tool(MAT)工具分析的過程。 1.生成dump檔案

     通過jvm引數--XX:-HeapDumpOnOutOfMemoryError可以讓JVM在出現記憶體溢位是Dump出當前的記憶體轉儲快照;
     用jmap生產dump檔案,win通過工作管理員檢視tomcat的程序pid,linux用ps命令檢視程序pid,然後用jmap命令   (Java5:jmap -heap:format=b <pid>;Java6:jmap -dump:format=b,file=HeapDump.bin <pid>)。
     生成之後結果如下:

   這說明在這個路徑下面生成了一個dump檔案 2.開始使用MAT進行OOM分析

第一步,啟動mat ,選擇File->Open Heap Dump 選擇你的dump檔案。下面開始等待,mat解析dump檔案需要花一些時間,在解析的同時會在硬碟上寫入一些解析結果檔案,這樣下次開啟時速度會快很多。有時候mat在解析過程中可能會出現出錯的情況,這個時候可以將那些臨時檔案刪除以後重試第一步,如果你的rp夠好的話重試也許會解析成功。 

第二步,檢視記憶體洩漏分析報表。mat解析完成以後會出現如下圖的提示: 
 
因為我們就是為了查詢記憶體洩漏的問題,所以保持預設選項直接點“Finish”就可以。 
3.分析圖解 通過MAT開啟dump出來的記憶體檔案,開啟後如下圖:

       
     從上圖可以看到它的大部分功能。
     1. Histogram可以列出記憶體中的物件,物件的個數以及大小。
     2. Dominator Tree可以列出那個執行緒,以及執行緒下面的那些物件佔用的空間。
     3.Top consumers通過圖形列出最大的object。
     4.Leak Suspects通過MA自動分析洩漏的原因。      Histogram如下圖:      Objects:類的物件的數量。      Shallow size:就是物件本身佔用記憶體的大小,不包含對其他物件的引用,也就是物件頭加成員變數(不是成員變數的值)的總和。      Retained size:是該物件自己的shallow size,加上從該物件能直接或間接訪問到物件的shallow size之和。換句話說,retained size是該物件被GC之後所能回收到記憶體的總和。      我們發現ThreadLocal和bingo.persister.dao.Daos類的物件佔用了很多空間。

       Dominator Tree如下圖:      我們發現quartz的定時器的工作執行緒(10個)佔了很多的記憶體空間


     Top consumers如下圖:      這裡顯示了記憶體中最大的物件有哪些,他們對應的類是哪些,類載入器classloader是哪些。      有些時候,我們在這裡就可以看到程式碼洩露的位置。
 
     Leak Suspects如下圖:      從那個餅圖,該圖深色區域被懷疑有記憶體洩漏,可以發現整個heap才250M記憶體,深色區域就佔了34%。後面的描述,告訴我們quartz執行緒佔用了大量記憶體,並指出system class loader載入的"java.lang.ThreadLocal"例項的記憶體中聚集(消耗空間),並建議用關鍵字"java.lang.ThreadLocal$ThreadLocalMap$Entry[]"進行檢查。所以,MAT通過簡單的報告就說明了問題所在。
 
通過Leak Suspects的Problem Suspect 1點選【Details »】, 如下圖如下圖所示的上下文選單中選擇 List objects -> with outgoning references, 檢視ThreadLocal都應用了些什麼物件。

  現在看到ThreadLocal中引用的物件如下圖: 是dao物件 ps:該dao物件包含一個輕量級的ORM關係內容,所以Retained size比較大
下面繼續檢視dao的gc ROOT 如下圖所示的上下文選單中選擇 Path To GC Roots -> exclude weak references, 過濾掉弱引用,因為在這裡弱引用不是引起問題的關鍵。
   從下圖中,可以看到在org.quartz.simpl.SimpleThreadPool中儲存了daos的引用。所以可以得出是是因為定時器在執行的過程中持有大量的Daos物件應起了記憶體洩露。為什麼會有那麼多的Daos呢,Daos不是一個無狀態的單例的、可以重用的嗎?繼續檢視spring配置檔案發現Daos的bean配置成scope="prototype",導致定時任務又是每次呼叫都生產新的Daos例項。由於是Daos是無狀態的,修改為單例的,問題解決。