使用Memory Analyse Tool分析内存溢出(非Eclipse插件)

Memory Analyse Tool是Java的dump文件分析工具,可以作为Eclipse的插件,也可以作为一个Windows下运行的软件单独使用,下面介绍一下MAT作为Windows软件的使用方式。

 

其实java的jvisualvm也可以用来分析dump文件,但是jvisualvm本身可以使用的系统内存不太好调,貌似需要.Net环境,我dump了一份1.5G的堆转储文件,用jvisualvm工具打不开,会报内存溢出,而MAT的内存可以在配置文件中调整。

 

一,MAT的下载安装

下载地址是:http://www.eclipse.org/mat/downloads.php


我选择的是Windows(x86_64),下载之后是个zip包,解压就直接可以使用


双击其中的MemoryAnalyzer.exe就可以运行此工具。

运行之后界面:



二,修改MAT可用内存

在MemoryAnalyzer.ini文件中可以修改MAT使用的内存大小


调整-Xmx属性,默认是1024M,这么设置没法打开1.5G的dump文件,我给改成了3024M。

 

三,dump文件获取

Linux环境下dump文件获取可以用以下命令:

jmap-dump:format=b,file=20170307.dump 16048

file后面的是自定义的文件名,最后的数字是进程的pid。

 

四,加载dump文件。

点击File-->Open Heap Dump,选择dump文件,MAT会加载并分析该dump文件,如果文件较大,可能需要一些时间。

开始分析时可以选择是否分析可能存在的问题,如果选择是,MAT会提供Problem Suspect,这个Problem Suspect如果你切到别的标签再切回来就没有了,可以通过点击Leak Suspects链接重新获得。

加载结束后的界面如下:


 

五,分析dump文件结果

不同的场景内存溢出的原因各不相同,我介绍一下我遇到这个场景

1,根据饼状图可以看到,进程一共占用了1.2G内存,有一个类(暗绿色)占了1G,这个类可能就是内存溢出的原因。

2,把鼠标移动到饼状图的暗绿色区域,在左侧会显示出该类的概要信息


3,左侧上方的Inspector栏显示了这个类的信息,可以看到这是NonRegisteringDriver类,在com.mysql.jdbc包下,后面还写着这个类的加载器,还有占用内存,这个NonRegisteringDriver是java在使用数据库的时候会用到的类,具体功能我以前也没研究过。

注意占用的内存实际上分成了两部分:112(shallow size)和1097869176(retained size)

shallow size这个表示类本身占用的内存大小,不包含其中引用的类占用的内存,而retained size代表包括其中引用类的内存,很小的类内存和很大的引用内存说明这个类里引用了特别占内存的对象。

4,左侧下方的Statics标签显示了这个类的静态属性,默认显示前25个,点击这一栏下方的+25 out of 28 displayed可以显示剩下的3个属性。

Class Hierarchy标签展示了这个类的继承关系:


5,点击右侧Actions栏的LeakSuspects按钮,MAT会提供Problem Suspect,如下图:


点击Details链接,可以看到关于这个类的很多信息,相当于一个小报表,比较长,分为几部分,我们重点关注第三部分,Accumulated Objects in Dominator Tree:


可以看到最顶层的就是NonRegisteringDriver类,经过层层引用,存在大量的com.mysql.jdbc.ByteArrayRow类,我不知道这个类是做什么用的,但看起来就是这个类的对象太多,导致内存占用过大。

点击其中任意一个com.mysql.jdbc.ByteArrayRow对象,比如我点了第一个,在左边的Attributes标签,会显示这个对象的属性:


然而根据这些属性我并不能知道这个类是干啥的,第一行的internalRowData属性,value值是一个byte类型的数组,可以在这一行上点击右键,选择go into,查看这个数组的详细信息:


从数组信息来看,有日期,有数字

另外,点击com.mysql.jdbc.ByteArrayRow类之后左边的Class Hierarchy标签,可以看到ByteArrayRow类的继承信息,


可以发现这个类是ResultSetRow类的子类,因此怀疑这个类实际上代表数据库查询出的ResultSet的一个元素。

ByteArrayRow类能发现的线索就这些,接下来可以点击他上层引用的类查看他们的Attributes标签,当看到[email protected] 0x78b85af40 这一行时,基本就已经确定了这次内存溢出的原因:


可以看到Attributes标签里面有一个originalSql属性,这个属性记录了这次查询的脚本,具体sql我就不贴了,其实就是一个全表查询,没有任何查询条件。

也就是说这次内存溢出的原因就是因为查询了数据库某个表全部的数据,把内存撑爆了。

 

另外我们可以看一下报表的倒数第二部分,Accumulated Objects by Class in Dominator Tree:


这一栏列出了引用最多的类,也就是之前看到的com.mysql.jdbc.ByteArrayRow类,有120多万个对象,我查了一下数据库,那个表里确实就是这么多数据,这也证明上面对内存溢出原因的判断是正确的。

 

至此,分析过程结束。

其实MAT这个工具支持的功能还有很多,上面介绍的只是一小部分功能,其他部分功能还可以继续挖掘。