使用jmap和MAT觀察Java程序內存數據

使用jmap和MAT觀察Java程序內存數據 html

背景

不少故障跟數據結構中實際存儲的數值會頗有關係。有時候咱們可以預感到這類數據,例如: java

  1. 某些 成員變量緩存池當前尺寸(size)、容積上限(capacity)
    爲了加快服務響應速度,一般會把一些相對靜態的內容在內存中作緩存。內存中容納這些內容的容器稱爲緩存池緩存池已存對象的數目,即緩存池當前尺寸。這個當前尺寸與該緩存池實際佔用內存量息息相關。
    因爲內存有限,緩存池一般會設置容積上限。這個值能夠控制緩存池 佔用內存量的最大值。 正則表達式

  2. 某些類的對象數量、某個對象致使的沒法回收內存量retained size緩存

  3. 局部變量的值 數據結構

有經驗的開發人員可能將這些數值打印到日誌中,便於故障分析。 oracle

但不少時候咱們並無預先打印這些數值,或者很計算出這些數值(如沒法回收內存量)。在沒有日誌記錄的狀況下,該如何得到這些數值? app

答案是使用jmapMAT(Memory Analyzer Tool)! eclipse

工具介紹

  • jmap是JDK自帶的命令行工具,用它能夠將指定進程的整個Java堆(Java Heap)轉儲到指定的數據文件
  • MAT是一個第三方工具,能夠從http://www.eclipse.org/mat/下載。必須在圖形界面(GUI)中執行(例如Windows或Linux的VNC桌面系統中)。用它能夠對數據文件作很是詳盡的分析。

它們之間的關係圖示以下: 函數

工具使用

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)後,一般能夠經過以下幾個方式來查看:

  1. Leak Suspect: Memory Analyzer智能分析出的最可能發生內存泄漏的對象,一般也就是佔用內存量(Retained Size)最大的對象
  2. Dominator Tree查看,經常使用的兩種方式:
    • 按照佔用內存量(Retained Size)從大到小排序(Dominator Tree的默認查看方式)
    • 正則表達式匹配類名,查看對應的類(Class)的對象(Object),或者對應類的靜態成員(Static Member)
  3. Threads View查看:能夠看到各個線程的調用棧,以及局部引用的值
  4. Object Query Language查詢:能夠用相似SQL的語句查詢整個Java Heap堆

使用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級別相對)的數據內容。包括全部類、對象的值、引用關係、線程調用棧、局部引用,以及每一個對象佔用內存量等信息。而這些信息對故障追蹤可能會起到很是大的幫助。

相關文章
相關標籤/搜索