使用jmap和MAT觀察Java程序內存數據 html
不少故障跟數據結構中實際存儲的數值會頗有關係。有時候咱們可以預感到這類數據,例如: java
某些 成員變量:緩存池的當前尺寸(size)、容積上限(capacity)
爲了加快服務響應速度,一般會把一些相對靜態的內容在內存中作緩存。內存中容納這些內容的容器稱爲緩存池。緩存池中已存對象的數目,即緩存池的當前尺寸。這個當前尺寸與該緩存池實際佔用內存量息息相關。
因爲內存有限,緩存池一般會設置容積上限。這個值能夠控制緩存池 佔用內存量的最大值。 正則表達式
某些類的對象數量、某個對象致使的沒法回收內存量(retained size) 緩存
局部變量的值 數據結構
有經驗的開發人員可能將這些數值打印到日誌中,便於故障分析。 oracle
但不少時候咱們並無預先打印這些數值,或者很計算出這些數值(如沒法回收內存量)。在沒有日誌記錄的狀況下,該如何得到這些數值? app
答案是使用jmap 和 MAT(Memory Analyzer Tool)! eclipse
它們之間的關係圖示以下: 函數
jmap使用方法: 工具
jmap -dump:file=dump.map <pid>
執行後,會生成dump.map文件。這個文件能夠用MAT打開。
若是你在遠程Linux系統中工做,可能須要使用VNC(具體方法參考: (TODO) )。
啓動MAT的方法(在Linux桌面中,打開一個終端(Terminal),而後執行):
#假設mat工具已經解壓到/usr/local/mat中 cd /usr/local/mat #若是dump.map文件尺寸較大(例如2GB以上),須要修改MemoryAnalyzer.ini文件中 #-Xmx選項。將它設置到一個更大的值,例如4GB左右: -Xmx4000m #啓動Memory Analyzer ./MemoryAnalyzer
啓動後,在菜單中選擇Open Heap Dump,讀入dump.map文件便可開始分析。
jmap生成的數據文件(例如dump.map)中有詳盡的Java堆(Java Heap)的信息,這些信息包括—全部的Java對象的數據成員,以及它們之間的引用關係等。而且MAT又是一個很是棒的工具,所以上述內容均可以看到。而且MAT還爲內存泄漏的分析有特別的支持,它可以計算出每一個對象的佔用內存量(Retained Size)。將對象按照佔用內存量(Retained Size)從大到小排序,一般很容易就能肯定到底是什麼對象發生了內存泄漏。
在MAT中打開數據文件(例如dump.map)後,一般能夠經過以下幾個方式來查看:
使用MAT不但能夠觀察(Inspect)全部對象的成員變量的值,若是變量是引用,還能夠跟蹤(follow)引用(reference)進一步查看相關對象的數據。
撰寫程序以下(MatExample.java):
import java.util.List; import java.util.LinkedList; public class MatExample { static String staticVar = "Hello, I am static"; private String instanceVar = "Hello, I am instance"; public static void main(String[] args) throws Exception { MatExample instance = new MatExample(); List<String> leakList = new LinkedList<String>(); int k = 0; for(int i=0; i<10000; i++) { k++; leakList.add(k+""); } System.out.println("10000 objects generated"); while(true) { Thread.sleep(1000); } } }
編譯(生成MatExample.class)
javac MatExample.java
運行(MatExample.class)
java MatExample
程序輸出:
1000 objects generated
另開一個命令行,而後使用jps列出全部Java進程:
jps -mlvV
jps輸出相似以下信息:
91888 sun.tools.jps.Jps -mlvV -Dapplication.home=E:\Java\jdk1.7.0_45 -Xms8m 90752 MatExample
上面90752即是MatExample的進程編號(PID),如今咱們運行jmap獲取數據文件(存儲到dump.map):
jmap -dump:file=dump.map 90752
接着啓動MAT,並經過菜單中 File->Open Heap Dump 打開dump.map,如圖:
MAT會讀取數據文件到內存中並做必要的預處理(若是是較大的文件,則耗時較長,建議去喝杯茶休息下)。
MAT讀取完文件,會出現一個嚮導,讓咱們選擇一種分析方式。咱們能夠選擇泄漏嫌疑對象報告(Leak Suspects Report)分析,而後點「Finish」。
MAT呈現出一個餅圖描述該進程內存的分佈狀況,在此圖形中能夠清晰地看到內存佔用最大的對象。下方,還會有Top N的泄露嫌疑對象文字描述,如圖:
這裏說的是局部變量佔用「721,136 (63.90%) bytes」,而且是一個LinkedList對象形成的。點擊「See stacktrace」,就能看到調用棧
main at java.lang.Thread.sleep(J)V (Native Method) at MatExample.main([Ljava/lang/String;)V (MatExample.java:21)
線程名稱是「main」。點擊圖示的按鈕,能夠打開線程視圖(Threads view),查看帶有局部變量的調用棧信息。
從中能夠清晰看到是在MatExample.main函數中引用的一個LinkedList佔用了720,040字節。
咱們能夠點擊菜單中Window -> Inspector,而後查看該LinkedList對象的屬性。
可見,其size屬性確實是10000。可見泄露嫌疑對象報告(Leak Suspect Report)可以幫助咱們較迅速地定位到內存泄漏點。
另外,也能夠從內存佔用最大的對象的思路來找內存問題,點擊圖示圖標,打開Dominator Tree View
將最上面的一行逐行展開,也能夠看到LinkedList的內存佔用較大。
下面嘗試查看指定類型(class)的靜態成員(static member)和對象
在Dominator Tree的正則輸入框(Regex)中輸入「MatExample」:
回車後,顯示下圖:
「class MatExample」的屬性和引用,與代碼中定義的MatExample類的靜態成員(static member)的內容相符合:
static String staticVar = "Hello, I am static";
咱們能夠嘗試下跟蹤引用:在Inspector窗口中,對一個引用點擊右鍵,而後選擇「Go Into」
便可看到該對象的屬性。
若是但願讓引用對象顯示在右圖中,能夠選擇"List objects -> with outgoing references」
這樣能夠更方便地進一步跟蹤其引用。
但咱們發現,代碼中建立的MatExample對象
MatExample instance = new MatExample();
並無出如今以前的圖中
能夠嘗試下Object Query Language (OQL)。點擊圖標
輸入查詢語句
select * from INSTANCEOF MatExample
點擊執行按鈕
如今咱們能夠查到全部MatExample對象(本例只有一個):
點選後,在Inspector窗口中,一樣能夠看到其成員變量的值。這裏就不詳述了。
jmap和MAT是一對很是強大的工具,經過它們能夠獲知一個Java進程中全部Java級別(與Native級別相對)的數據內容。包括全部類、對象的值、引用關係、線程調用棧、局部引用,以及每一個對象佔用內存量等信息。而這些信息對故障追蹤可能會起到很是大的幫助。