1. 程式人生 > >使用 Eclipse Memory Analyzer 進行堆轉儲檔案分析

使用 Eclipse Memory Analyzer 進行堆轉儲檔案分析

概述

對於大型 JAVA 應用程式來說,再精細的測試也難以堵住所有的漏洞,即便我們在測試階段進行了大量卓有成效的工作,很多問題還是會在生產環境下暴露出來,並且很難在測試環境中進行重現。JVM 能夠記錄下問題發生時系統的部分執行狀態,並將其儲存在堆轉儲 (Heap Dump) 檔案中,從而為我們分析和診斷問題提供了重要的依據。

通常記憶體洩露分析被認為是一件很有難度的工作,一般由團隊中的資深人士進行。不過,今天我們要介紹的 MAT(Eclipse Memory Analyzer)被認為是一個“傻瓜式“的堆轉儲檔案分析工具,你只需要輕輕點選一下滑鼠就可以生成一個專業的分析報告。和其他記憶體洩露分析工具相比,MAT 的使用非常容易,基本可以實現一鍵到位,即使是新手也能夠很快上手使用。

MAT 的使用是如此容易,你是不是也很有興趣來親自感受下呢,那麼第一步我們先來安裝 MAT。

準備環境和測試資料

我們使用的是 Eclipse Memory Analyzer V0.8,Sun JDK 6

安裝 MAT

和其他外掛的安裝非常類似,MAT 支援兩種安裝方式,一種是“單機版“的,也就是說使用者不必安裝 Eclipse IDE 環境,MAT 作為一個獨立的 Eclipse RCP 應用執行;另一種是”整合版“的,也就是說 MAT 也可以作為 Eclipse IDE 的一部分,和現有的開發平臺整合。

整合版的安裝需要藉助 Update Manager。

如圖 1 所示,首先通過 Help -> Software Updates... 啟動軟體更新管理嚮導。

圖 1. 安裝外掛第一步
圖 1. 安裝外掛第一步
圖 2. 安裝外掛第二步
圖 2. 安裝外掛第二步

如圖 3 所示,接下來選擇你想要安裝的 MAT 的功能點,需要注意的是 Memory Analyzer (Chart) 這個功能是一個可選的安裝專案,它主要用來生成相關的報表,不過如果需要用到這個功能,你還需要額外的安裝 BIRT Chart Engine。

圖 3. 安裝外掛第三步
圖 3. 安裝外掛第三步

外掛安裝完畢,你還需要重新啟動 Eclipse 的工作平臺。

比較而言,單機版的安裝方式非常簡單,使用者只需要下載相應的安裝包,然後解壓縮即可執行,這也是被普遍採用的一種安裝方式。在下面的例子裡,我們使用的也是單機版的 MAT。具體的下載要求和地址可參見其產品下載頁面:

http://www.eclipse.org/mat/downloads.php

另外,如果你需要用 MAT 來分析 IBM JVM 生成的 dump 檔案的話,還需要額外安裝 IBM Diagnostic Tool Framework ,具體的下載和安裝配置步驟請參見:http://www.ibm.com/developerworks/java/jdk/tools/dtfj.html

配置環境引數

安裝完成之後,為了更有效率的使用 MAT,我們還需要做一些配置工作。因為通常而言,分析一個堆轉儲檔案需要消耗很多的堆空間,為了保證分析的效率和效能,在有條件的情況下,我們會建議分配給 MAT 儘可能多的記憶體資源。你可以採用如下兩種方式來分配記憶體更多的記憶體資源給 MAT。

一種是修改啟動引數 MemoryAnalyzer.exe -vmargs -Xmx4g

另一種是編輯檔案 MemoryAnalyzer.ini,在裡面新增類似資訊 -vmargs – Xmx4g。

至此,MAT 就已經成功地安裝配置好了,開始進入實戰吧。

獲得堆轉儲檔案

巧婦難為無米之炊,我們首先需要獲得一個堆轉儲檔案。為了方便,本文采用的是 Sun JDK 6。通常來說,只要你設定瞭如下所示的 JVM 引數:

-XX:+HeapDumpOnOutOfMemoryError

JVM 就會在發生記憶體洩露時抓拍下當時的記憶體狀態,也就是我們想要的堆轉儲檔案。

如果你不想等到發生崩潰性的錯誤時才獲得堆轉儲檔案,也可以通過設定如下 JVM 引數來按需獲取堆轉儲檔案。

-XX:+HeapDumpOnCtrlBreak

除此之外,還有很多的工具,例如 JMap,JConsole 都可以幫助我們得到一個堆轉儲檔案。本文例項就是使用 JMap 直接獲取了 Eclipse Galileo 程序的堆轉儲檔案。您可以使用如下命令:

JMap -dump:format=b,file=<dumpfile> <pid>

不過,您需要了解到,不同廠家的 JVM 所生成的堆轉儲檔案在資料儲存格式以及資料儲存內容上有很多區別, MAT 不是一個萬能工具,它並不能處理所有型別的堆儲存檔案。但是比較主流的廠家和格式,例如 Sun, HP, SAP 所採用的 HPROF 二進位制堆儲存檔案,以及 IBM 的 PHD 堆儲存檔案等都能被很好的解析(您需要安裝額外的外掛,請參考 相關說明,本文不作詳細解釋)。

萬事俱備,接下來,我們就可以開始體驗一鍵式的堆儲存分析功能了。

生成分析報告

首先,啟動前面安裝配置好的 Memory Analyzer tool , 然後選擇選單項 File- Open Heap Dump 來載入需要分析的堆轉儲檔案。檔案載入完成後,你可以看到如圖 4 所示的介面:

圖 4. 概覽
圖 4. 概覽

通過上面的概覽,我們對記憶體佔用情況有了一個總體的瞭解。先檢查一下 MAT 生成的一系列檔案。

圖 5. 檔案列表
圖 5. 檔案列表

可以看到 MAT 工具提供了一個很貼心的功能,將報告的內容壓縮打包到一個 zip 檔案,並把它存放到原始堆轉儲檔案的存放目錄下,這樣如果您需要和同事一起分析這個記憶體問題的話,只需要把這個小小的 zip 包發給他就可以了,不需要把整個堆檔案發給他。並且整個報告是一個 HTML 格式的檔案,用瀏覽器就可以輕鬆開啟。

接下來我們就可以來看看生成的報告都包括什麼內容,能不能幫我們找到問題所在吧。您可以點選工具欄上的 Leak Suspects 選單項來生成記憶體洩露分析報告,也可以直接點選餅圖下方的 Reports->Leak Suspects 連結來生成報告。

圖 6. 工具欄選單
圖 6. 工具欄選單

分析三步曲

通常我們都會採用下面的“三步曲”來分析記憶體洩露問題:

首先,對問題發生時刻的系統記憶體狀態獲取一個整體印象。

第二步,找到最有可能導致記憶體洩露的元凶,通常也就是消耗記憶體最多的物件

接下來,進一步去檢視這個記憶體消耗大戶的具體情況,看看是否有什麼異常的行為。

下面將用一個基本的例子來展示如何採用“三步曲”來檢視生產的分析報告。

檢視報告之一:記憶體消耗的整體狀況

圖 7. 記憶體洩露分析報告
圖 7. 記憶體洩露分析報告

如圖 7 所示,在報告上最醒目的就是一張簡潔明瞭的餅圖,從圖上我們可以清晰地看到一個可疑物件消耗了系統 99% 的記憶體。

在圖的下方還有對這個可疑物件的進一步描述。我們可以看到記憶體是由 java.util.Vector 的例項消耗的,com.ibm.oti.vm.BootstrapClassLoader 負責這個物件的載入。這段描述非常短,但我相信您已經可以從中找到很多線索了,比如是哪個類佔用了絕大多數的記憶體,它屬於哪個元件等等。

接下來,我們應該進一步去分析問題,為什麼一個 Vector 會佔據了系統 99% 的記憶體,誰阻止了垃圾回收機制對它的回收。

檢視報告之二:分析問題的所在

首先我們簡單回顧下 JAVA 的記憶體回收機制,記憶體空間中垃圾回收的工作由垃圾回收器 (Garbage Collector,GC) 完成的,它的核心思想是:對虛擬機器可用記憶體空間,即堆空間中的物件進行識別,如果物件正在被引用,那麼稱其為存活物件,反之,如果物件不再被引用,則為垃圾物件,可以回收其佔據的空間,用於再分配。

在垃圾回收機制中有一組元素被稱為根元素集合,它們是一組被虛擬機器直接引用的物件,比如,正在執行的執行緒物件,系統呼叫棧裡面的物件以及被 system class loader 所載入的那些物件。堆空間中的每個物件都是由一個根元素為起點被層層呼叫的。因此,一個物件還被某一個存活的根元素所引用,就會被認為是存活物件,不能被回收,進行記憶體釋放。因此,我們可以通過分析一個物件到根元素的引用路徑來分析為什麼該物件不能被順利回收。如果說一個物件已經不被任何程式邏輯所需要但是還存在被根元素引用的情況,我們可以說這裡存在記憶體洩露。

現在,讓我們開始真正的尋找記憶體洩露之旅,點選“Details ”連結,可以看到如圖 8 所示對可疑物件 1 的詳細分析報告。

圖 8. 可疑物件 1 的詳細分析報告
圖 8. 可疑物件 1 的詳細分析報告
  1. 我們檢視下從 GC 根元素到記憶體消耗聚集點的最短路徑:
圖 9. 從根元素到記憶體消耗聚集點的最短路徑
圖 9. 從根元素到記憶體消耗聚集點的最短路徑

我們可以很清楚的看到整個引用鏈,記憶體聚集點是一個擁有大量物件的集合,如果你對程式碼比較熟悉的話,相信這些資訊應該能給你提供一些找到記憶體洩露的思路了。

接下來,我們再繼續看看,這個物件集合裡到底存放了什麼,為什麼會消耗掉如此多的記憶體。

圖 10. 記憶體消耗聚集物件資訊
圖 10. 記憶體消耗聚集物件資訊

在這張圖上,我們可以清楚的看到,這個物件集合中儲存了大量 Person 物件的引用,就是它導致的記憶體洩露。

至此,我們已經擁有了足夠的資訊去尋找洩露點,回到程式碼,我們發現,是下面的程式碼導致了記憶體洩露 :

清單 1. 記憶體洩漏的程式碼段
 while (1<2) 
 { 
			
	 Person person = new Person("name","address",i); 
	 v.add(person); 
	 person = null; 
 }

總結

從上面的例子我們可以看到用 MAT 來進行堆轉儲檔案分析,尋找記憶體洩露非常簡單,尤其是對於新手而言,這是一個很好的輔助分析工具。但是,MAT 絕對不僅僅是一個“傻瓜式”記憶體分析工具,它還提供很多高階功能,比如 MAT 支援用 OQL(Object Query Language)對 heap dump 中的物件進行查詢,支援對執行緒的分析等,有關這些功能的使用可以參考 MAT 的幫助文件。