使用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级别相对)的数据内容。包括全部类、对象的值、引用关系、线程调用栈、局部引用,以及每一个对象占用内存量等信息。而这些信息对故障追踪可能会起到很是大的帮助。