概述
MAT (Memory Analyzer Tool ) 工具是一款功能强大的Java堆内存分析器.可以用于查找内存泄露以及查看内存消耗情况.
MAT是基于Eclipse开发的,不仅仅可以单独使用,还可以作为插件的形式内嵌在Eclipse中使用,是一款免费的性能分析工具,使用起来非常的方便.
MAT主要的功能就是分析dump文件的
注意:如果单独使用,那么解压即可用,不需要安装即可
不用安装,直接双击MemoryAnalyzer.exe就可以启动了.
分析堆dump文件
MAT可以分析 heap dump文件,在进行内存分析时,只要获得了反映当前设备内存映像的hprof文件,通过MAT打开就可以直观地看到当前的内存信息.
一般来说,这些内存信息包括:
- 所有的对象信息,包括对象实例、成员变量、存储于栈中的基本类型值和存储在堆中的其它对象的引用值。
- 所有的类信息,包括classloader、类名称、父类、静态变量等等
- GCRoot所有的这些对象的引用路径
- 线程信息,.包括线程的调用栈及此线程的线程局部变量(TLS)
几点说明
- MAT不是一个万能的工具,并不能处理所有类型的堆存储文件.但是比较主流的厂家和格式,;列如SUN,HP,SAP所采用的HPROF二进制堆存储文件,以及IBM的PHD堆存储文件等都能被MAT很好的解析.也许某个冷门的公司的二进制堆存储文件就无法识别.这个需要大家注意一下
- MAP最吸引人的地方就是生成内存泄露的报表,方便定位问题和分析问题.虽然说MAT有这么强大的功能,但是内存分析也没有简单到一键完成的程度,很多内存问题还是需要我们从MAT展现给我们的信息当中通过经验和直觉来判断才能发现.
获取DUMP文件的方式
方式1:使用jmap工具生成,可以生成任意一个Java进程的dump文件.
方式2:通过配置jvm参数生成.
对比:考虑到生产环境中几乎不可能在线对其进行分析,大多都是采用离线分析,因此使用jmap+MAT工具是最常见的组合
方式三: 使用VisualVM 可以导出堆dump文件
方式四: 使用MAT既可以打开一个已有的堆快照,也可以通过MAT直接从活动的Java程序中导出堆快照.这个功能借助于jps列出当前正在运行的Java进程,以供选择并获取快照.
基本使用案例
案例代码
import java.util.ArrayList;
import java.util.Random;
/**
* -Xms600m -Xmx600m -XX:SurvivorRatio=8
*/
public class OOMTest {
public static void main(String[] args) {
ArrayList<Picture> list = new ArrayList<>();
while(true){
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add(new Picture(new Random().nextInt(100 * 50)));
}
}
}
class Picture{
private byte[] pixels;
public Picture(int length) {
this.pixels = new byte[length];
}
}
启动上面的代码,
打开已经生成好的hprof文件
导出hprof文件
用eclipse的MAT插件打开
会出现下面的界面,直接点cancel就可以了 ,点finish也行
从进程中生成dump文件快照
根据进程号选择你要查看的进程
点击finish即可
Getting Started Wizard介绍
leak suspects report 泄露的疑点报告
这个是mat会自动的检测dump文件,用于查看哪些是泄露的疑点,报告中会说明哪些对象还在存活,以及为什么没有被垃圾回收器收集.<br /> 内存泄露的问题非常关注这个,因为内存泄露就是对象不用了,但是为什么不能被垃圾回收器回收.
Component Report 组件的报告
会分析对象的集合,找到相关的可疑的内存空间,比如说重复的字符串,空的集合容器,弱引用等等,这些都是我们认为可疑的内存
Re-open previously run reports 重新打开之前运行的 reports
之前存在的reports会和dump文件在同一个目录下的zip文件中
就是下面的文件:
MAT界面的主要功能
<br />下面会依次介绍下面图片的意思<br /> ![image.png](https://cdn.nlark.com/yuque/0/2022/png/350923/1658038701861-44df2a7e-2ac8-40b2-8ccb-6ee402f8a3d5.png#clientId=ua9bbc8ad-333d-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=786&id=u6b7ac86b&margin=%5Bobject%20Object%5D&name=image.png&originHeight=786&originWidth=1347&originalType=binary&ratio=1&rotation=0&showTitle=false&size=342556&status=done&style=none&taskId=u648e6680-1097-4294-8b34-4422f97d526&title=&width=1347)
Size是当前dump文件的大小,
Classes是dump文件中加载的类的个数是708个,
Objects是dump文件中创建了46000个对象.46K就是46*1000,也就是46000
Class Loader是dump文件中使用的类加载器有四个
Unreadchable Objects Histogram 是不可达的对象的直方图
这个是保留的堆快照中内存中最大的对象是多少,你把鼠标点在哪里,然后左边Inspector就显示啥
第一行: 是当前线程的编号
第二行: 不知道
第三行: class java.lang.Thread 就是当前的线程,0xf3801c08是当前线程的编号
第四行: java.lang.Object是父类的信息
第五行:
第六行: shallow 是浅堆
第七行: retained 是深堆
第八行:
堆文件的Overview:
第一行: dump文件的大小
第二行:对象的个数
第三行:类的个数
第四行:类加载器的个数
第五行:Gc roots 对象个数
第六行: 文件的类型
分析堆dump文件
一般分析饼状图的时候先从最大的查看,当然也有可能这个最大的就是我们希望保存在堆里面的数据,那么我们就接着看饼状图第二大的数据,或者第三大的数据,这样就根据自己的项目情况看看哪个是有内存泄露的疑点的,分析出来之后,然后解决一下就完了.
MAT中Histogram的功能演示
具体内容:
内存中加载的类, Objects是对象的个数, Shallow Heap是占用的浅堆大小, Retained Heap 是占用的深堆的大小
包分组
如果显示的类过多的话,也可以用根据包名来分组,分组完了查看就更方便看了.
查看目标的gc root
假如说我想看 picture 的gc root ,就可以用 Merge Shortest Paths to GC Roots, 最右面可以排除 引用选项 ,点击查看,就能看到下面 ArrayList对象 0xf663aab8的对象引用了那么多Picture对象,然后你就需要进行分析这些引用是不是合理的,如果不合理的话就尝试着优化它.
对比两个hprof文件的 Histogram值
在b.hprof文件里面点击上面的操作,选择a.hprof文件,然后点ok
然后就基于b.hprof文件去看和a.hprof的区别,可以看到b.hprof比a.hprof多了很多对象信息 +号代表b.hprof比a.hprof多了. 反之 减号 代表少.
比如说第一行byte[] +27884 的意思就是 b.hprof的byte[] 对象比 a.hprof的byte[] 的Objects多了27884个
通过这个功能你就可以观察指定时间内哪个对象增长了多少, 比如说下午三点导出a.hprof文件,然后下午四点导出b.hprof文件, 然后通过对比来看看下午四点导出的hprof文件比下午三点导出的hprof文件里面哪个Class Name的指标增加的比较快,这个有可能就是我们要担心的可能会出现oom的类了.
thread overview
展示线程的概述情况,我们可以查看当前进程中所有的Java的线程的情况,还可以查看当前的线程中栈祯里面保存的局部变量的信息.
点击图标:
点击之后显示当前dump文件有六个线程
main方法下面的
上图String 就是下面这个main方法的形参,ArrayList就是list对象
再点看ArrayList 就能看到ArrayList里面有个elementData ,这个elementData 是ArrayList源码里面存数据的数组容器,然后你就会发现elementData类型是Object[]类型 ,并且elementData里面有总共25个Picture对象,
我们读线程局部变量数据的目的就是为了分析哪些手可能会有泄露的问题.
查看这个对象引用过谁 和 查看谁引用过这个对象
第一个是出引用(就是这个对象都引用过谁) 第二个是入引用(哪些对象引用过来的)
下面展示的是ArrayList引用了哪些对象
还可以接着看哪些对象引用了这个对象了, 来确定为什么他还没被回收,我们就看看这个指针有没有必要存在,如果没有必要存在的话, 就改成弱引用等等.
然后查看,只有ArrayList里面的elementData数组引用了这个Picture 0xf399eb18 指针了,这样就没事儿了, 当然如果我们发现这个Picture对象除了被ArrayList引用了,还被别的对象引用了, 那么我们就要担心了,可能会有内存泄露的问题了.
因为案例代码的ArrayList的生命周期挺短的,main方法执行完了,ArrayList就可以被回收了,只要ArrayList挂掉了,那么ArrayList引用的这个Picture也会被挂掉.
当然下面的代码有while (true )死循环,实际情况下代码基本不会有while(true)这种缺心眼的写法,所以main方法结束了ArrayList就可以被回收了.
但是,如果你Picture对象还是被一个生命周期比较长的对象A引用了,ArrayList销毁了,但是这个对象A还在引用Picture对象,导致Picture无法被销毁回收, 就会存在泄露.
如果必须要被对象A引用,那么你就可以考虑把这个生命周期比较长的对象A引用Picture对象的引用改成弱引用就可以了.