目录
一、概述
二、获取dump文件的四种方式
三、MAT主要功能概览
四、Histogram类直方图功能演示
五、Thread Overview线程概览
六、深堆与浅堆
七、支配树的理解与应用
一、概述
MAT(Memory Analyzer Tool)工具是一款功能强大的Java堆内存分析器,可以用于查找内存泄漏以及查看内存消耗情况。MAT是基于Eclipse开发的,不仅可以单独使用,还可以作为插件的形式嵌入在Eclipse中使用,是一款免费的性能分析工具,使用起来非常方便。
MAT可以分析heap dump文件。在进行内存分析时,只要获得了反映当前设备内存映像的hprof文件,通过MAT打开就可以直观地看到当前的内存信息。一般说来,这些内存信息包含:
- 所有的对象信息,包括对象实例、成员变量、存储于栈中的基本类型值和存储于堆中的其他对象的引用值;
- 所有的类信息,包括classloader、类名称、父类、静态变量等;
- GCRoot到所有的这些对象的引用路径;
- 线程信息,包括线程的调用栈及此线程的线程局部变量(TLS);
MAT 不是一个万能工具,它并不能处理所有类型的堆存储文件。但是比较主流的厂家和格式,例如Sun,HP,SAP 所采用的 HPROF二进制堆存储文件,以及 IBM的 PHD 堆存储文件等都能被很好的解析。
最吸引人的还是能够快速为开发人员生成内存泄漏报表,方便定位问题和分析问题。虽然MAT有如此强大的功能,但是内存分析也没有简单到一键完成的程度,很多内存问题还是需要我们从MAT展现给我们的信息当中通过经验和直觉来判断才能发现。
官方地址: Eclipse Memory Analyzer Open Source Project | The Eclipse Foundation
二、获取dump文件的四种方式
【a】通过前面介绍的jmap指令生成,可以生成任意一个java进程的dump文件;
【b】通过配置JVM参数生成
- -XX:+HeapDumpOnOutOfMemoryError:在堆溢出的时候可以储存快照;
- -XX:HeapDumpPath=d:/dumps/:导出内存快照时保存的路径;
对比:考虑到生产环境中几乎不可能在线对其进行分析,大都是离线分析,因此采用jmap + MAT工具是最常见的组合。
【c】使用VisualVM可以导出堆dump文件;
【d】使用MAT既可以打开一个已有的堆快照,也可以通过MAT直接从活动的java程序汇总导出堆快照。该功能将借助jps列出当前正在运行的Java进程,以供选中并获取快照。如下图:
三、MAT主要功能概览
这里还是拿之前OOM的代码进行演示:
public class GCTest {
public static void main(String[] args) throws ClassNotFoundException {
List<byte[]> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
byte[] arr = new byte[1024 * 100];
list.add(arr);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
指定JVM参数:-Xms60m -Xmx60m,启动程序,使用jmap dump出堆转储快照。
C:\Users\QDM>jmap -dump:format=b,file=d:\mat_log\a.hprof 16548
Dumping heap to D:\mat_log\a.hprof ...
Heap dump file created
使用MAT打开刚生成的a.hprof堆转储快照,如下是一些功能描述:
【a】概览:堆空间大小、类、内存泄漏疑点等等
【b】类直方图
【c】内存泄漏疑点:提供内存泄露疑点占用内存大小,被谁加载的,以及类型等详细信息
【d】支配树:提供程序中最占内存的对象 (described later in the article)
【e】线程概览
【f】Heap Dump堆转储快照:提供堆dump文件的详细信息
四、Histogram类直方图功能演示
如下图是类直方图,它提供每个类的对象统计:
每一列的含义:
- Objects:类的对象的数量;
- Shallow size(浅堆大小):就是对象本身占用内存的大小,不包含对其他对象的引用,也就是对象头加成员变量(不是成员变量的值)的总和;
- Retained size(深堆大小):是该对象自己的shallow size,加上从该对象能直接或间接访问到对象的shallow size之和。换句话说,retained size是该对象被GC之后所能回收到内存的总和;
它按类名将所有的实例对象列出来,可以点击表头进行排序,在表的第一行可以输入正则表达式来匹配结果:
在某一项上右键打开菜单选择 list objects -> with incoming refs 将列出该类的实例:
它展示了对象间的引用关系。快速找出某个实例没被释放的原因,可以右健 Path to GC Roots-->exclue all phantom/weak/soft etc. reference :
得到的结果是:
用这个方法可以快速找到某个对象的 GC Root,一个存在 GC Root的对象是不会被 GC回收掉的。
MAT同时还提供了分组的功能,可以按照类、包名等进行分组查看,实际项目中,我们可以通过包名,能快速定位到我们项目中自定义的哪些实体类实例过多,方便排查问题。
五、Thread Overview线程概览
作用:
- 查看系统的Java线程;
- 查看局部变量的信息;
如下图:
线程概览主要用于分析内存泄漏,分析哪些对象可能存在内存泄漏,如上图,MAT提示可疑的线程是0xfdc9e0c8,也就是当前main线程有可能存在内存泄漏。
六、深堆与浅堆
(一)、浅堆(Shallow Heap)
浅堆是指一个对象所消耗的内存。在32位系统中,一个对象引用会占据4个字节,一个int类型会占据4个字节,long型变量会占据8个字节,每个对象头需要占用8个字节。根据堆快照格式不同,对象的大小可能会同8字节进行对齐(即对象的大小可以被8整除)。
以String为例:2个int值共占8字节,对象引用占用4字节,对象头8字节,合计20字节,向8字节对齐,故占24字节。(jdk7中)
int | hash32 | 0 |
int | hash | 0 |
ref | value | C:\Users\Administrat |
这24字节为String对象的浅堆大小。它与String的value实际取值无关,无论字符串长度如何,浅堆大小始终是24字节。
(二)、保留集(Retained Set)
对象A的保留集指当对象A被垃圾回收后,可以被释放的所有的对象集合(包括对象A本身),即对象A的保留集可以被认为是只能通过对象A被直接或间接访问到的所有对象的集合。通俗地说,就是指仅被对象A所持有的对象的集合。
(三)、深堆(Retained Heap)
深堆是指对象的保留集中所有的对象的浅堆大小之和。
注意:浅堆指对象本身占用的内存,不包括其内部引用对象的大小。一个对象的深堆指只能通过该对象访问到的(直接或间接)所有对象的浅堆之和,即对象被回收后,可以释放的真实空间。
(四)、对象的实际大小
对象的实际大小定义为一个对象所能触及的所有对象的浅堆大小之和,也就是通常意义上我们说的对象大小。与深堆相比,似乎这个在日常开发中更为直观和被人接受,但实际上,这个概念和垃圾回收无关。
下图显示了一个简单的对象引用关系图,对象A引用了C和D,对象B引用了C和E。那么对象A的浅堆大小只是A本身,不含C和D,而A的实际大小为A、C、D三者之和。而A的深堆大小为A与D之和,由于对象C还可以通过对象B访问到,因此不在对象A的深堆范围内。
七、支配树的理解与应用
MAT提供了一个称为支配树(Dominator Tree)的对象图。支配树体现了对象实例间的支配关系,可以列出那个线程,以及线程下面的那些对象占用的空间。
在对象引用图中,所有指向对象B的路径都经过对象A,则认为对象A支配对象B。如果对象A是离对象B最近的一个支配对象,则认为对象A为对象B的直接支配者。支配树是基于对象间的引用图所建立的,它有以下基本性质:
- 对象A的子树(所有被对象A支配的对象集合)表示对象A的保留集(retained set),即深堆;
- 如果对象A支配对象B,那么对象A的直接支配者也支配对象B;
- 支配树的边与对象引用图的边不直接对应;
如下图所示:
- 左图表示对象引用图,右图表示左图所对应的支配树。
1、对象A和B由根对象直接支配,由于在到对象C的路径中,可以经过A,也可以经过B,因此对象C的直接支配者也是根对象;
2、对象F与对象D相互引用,因为到对象F的所有路径必然经过对象D,因此,对象D是对象F的直接支配者;
3、而到对象D的所有路径中,必然经过对象C,即使是从对象F到对象D的引用,从根节点出发,也是经过对象C的,所以,对象D的直接支配者为对象C;
4、同理,对象E支配对象G。到达对象H的可以通过对象D,也可以通过对象E,因此对象D和E都不能支配对象H,而经过对象C既可以到达D也可以到达E,因此对象C为对象H的直接支配者;