概述
MAT,下载地址,Eclipse Memory Analysis Tools,一个分析Java堆数据的专业工具,可以计算出内存中对象的实例数量、占用空间大小、引用关系等,可得知哪些线程阻止垃圾收集器的回收工作,从而定位内存泄漏的原因。
什么时候会用到MAT?
- OutOfMemoryError时,触发Full GC,但空间却回收不了,引发内存泄露
- Java服务器系统异常,如load飙高,io异常,或线程死锁等,都可能通过分析堆中的内存对象来定位原因
分析堆转储文件需要消耗很多的堆空间,为保证分析的效率和性能,建议给MAT分配尽可能多的内存资源。两种方式:
- 修改启动参数
MemoryAnalyzer.exe -vmargs -Xmx4g
- 编辑文件
MemoryAnalyzer.ini
添加-vmargs – Xmx4g
生成堆转储文件
简单来说使用jmap -dump
命令可以生成堆转储文件。首先需要执行进程ID,即PID。通过top
或jps
命令拿到Java进程pid
。
具体执行命令:jmap -dump:format=b,file=heap.hprof <pid>
如果执行命令报错:
Unable to open socket file: target process not responding or HotSpot VM not loaded.The -F option can be used when the target process is not responding
则执行命令:jmap -F -dump:format=b,file=heap.hprof <pid>
命令执行时,JVM会将整个heap的信息dump写入到一个文件,heap如果比较大的话,就会导致这个过程比较耗时,且执行过程中为保证dump的信息是可靠的,会暂停应用。
可通过-d64
来解决:jmap -F -J -d64 -dump:format=b,file=dump.bin PID
一般dump下来的文件有几个G,如果dump下来的文件只有一两百M,说明jmap命令执行有问题,需多执行几次才能得出正常结果,这个时候可以选用gcore
把整个内存dump出来,然后再使用jmap把core dump转换成heap dump。
做法就是用gcore 1234命令来生成c版的core文件,再用命令jmap -dump:format=b,file=heap.hprof /bin/java core.1234.
概念
Shallow Size
Shallow size就是对象本身占用内存的大小,不包含其引用的对象。常规对象(非数组)的 Shallow size 由其成员变量的数量和类型决定。数组的 shallow size 由数组元素的类型(对象类型、基本类型)和数组长度决定。
在 32 位系统上,对象头占用 8 字节。int 占用 4 字节,不管成员变量(对象或数组)是否引用其他对象(实例)或赋值为 null 它始终占用 4 字节。对于 String 对象实例来说,它有三个 int 成员(34=12 字节)、一个 char[] 成员(14=4 字节)以及一个对象头(8 字节),总共 34 +14+8=24 字节。
注意JDK 版本区别:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** The offset is the first index of the storage that is used. */
private final int offset;
/** The count is the number of characters in the String. */
private final int count;
/** Cache the hash code for the string */
private int hash; // Default to 0
}
Retained Size
对象的 Retained Size = 对象本身的 Shallow Size + 对象能直接或间接访问到对象的 Shallow Size。即,Retained Size 就是该对象被 Gc 之后所能回收内存的总和。即,该对象到其他对象有引用关系并且该引用对象到 Gc Root 节点是不可达的。总之,Retained size 是一个整体度量,能反映内存结构和对象图的依赖关系,还可以找到根节点。在进行GC时,如果实例对象到 Gc Root 是不可达的,那么该对象会被回收。
Heap Size
堆的大小,当资源增加,当前堆的空间不够时,系统会增加堆的大小,若超过上限(如 64M,阈值视平台而定)则会被杀掉 。
Allocated
堆中已分配的大小,即 App 应用实际占用的内存大小,资源回收后,此项数据会变小。
建议:若单一操作反复进行,堆大小一直增加,则有内存泄露的隐患,可采用 MAT 进一步查看。
问题
Java heap space
打开dump1.phrof
文件失败,报错信息如下:
解决方案:
编辑MemoryAnalyzer.ini
文件,修改-Xmx1024m
为-Xmx2048m
,当然也可以修改为其他数值,大于1024即可,然后得是2的n次方。参考链接见文末。
The HPROF parser encountered a violation
打开dump1.phrof
文件失败,报错信息如下:
详细的报错日志:
The HPROF parser encountered a violation of the HPROF specification that it could not safely handle. This could be due to file truncation or a bug in the JVM. Please consider filing a bug at eclipse.org. To continue parsing the dump anyway, you can use -DhprofStrictnessWarning=true or set the strictness mode under Preferences > HPROF Parser > Parser Strictness. See the inner exception for details.
The HPROF parser encountered a violation of the HPROF specification that it could not safely handle. This could be due to file truncation or a bug in the JVM. Please consider filing a bug at eclipse.org. To continue parsing the dump anyway, you can use -DhprofStrictnessWarning=true or set the strictness mode under Preferences > HPROF Parser > Parser Strictness. See the inner exception for details.
(Possibly) Invalid HPROF file: Expected to read another 3,458,508,632 bytes, but only 125,883,023 bytes are available for heap dump record 0x21 at 0x8114f34a.
(Possibly) Invalid HPROF file: Expected to read another 3,458,508,632 bytes, but only 125,883,023 bytes are available for heap dump record 0x21 at 0x8114f34a.
解决方案:
考虑到上面的问题1,没有Google搜索,直接在配置文件MemoryAnalyzer.ini
里面增加一行配置:-DhprofStrictnessWarning=true
,重启MAT,重新打开dump1.phrof
文件,解析成功。
实战
打开一个.hprof
文件,主要有三个模块,左侧顶部的Inspector,左侧底部如下信息:
右侧主面板:
- 工具栏及Overview
- Details:概要信息,如空间大小、类的数量、对象实例数量、类加载器等
- Biggest Objects by Retained Size:以饼状图给出大对象,鼠标移动到饼图某个区域上方,在左侧会看到对象的详细信息
- Atcion提供多种分析维度:
- Histogram:按类列出内存中的对象,对象的个数以及大小
- Dominator Tree:支配树,分析对象的引用关系
- Top Consumers:
- Duplicate Classes:
- Reports:
- Leak Suspects:分析内存泄漏
- Top Components:
- Step By Step:
概念:
Shallow Heap:类对象本身占用内存大小,不包含其引用的对象内存
Retained Heap:对象自己占用内存 + 关联引用对象占用大小。相对于shallow heap,RetainedHeap可以更精确的反映一个对象实际占用的大小(因为如果该对象释放,retained heap都可以被释放)。
Retained Set:指这个对象本身和他持有引用的对象以及这些引用对象的Retained Set所占内存大小的总和
Histogram
该视图以Class类的维度展示每个Class类的实例存在的个数、占用的[Shallow内存]和[Retained内存]大小,可分别降序显示(升序没有意义),尤其是对Retained内存降序展示,
还可以按照对象或Class引用和被引用展示:List objects or Show objects by class:
- with incoming references:列出哪些类引入该类
- with outgoing references:出该类引用哪些类
多数情况下,在Histogram视图看到实例对象数量比较多的类都是一些基础类型,如char[](因为其构成String)、String、byte[],所以仅从这些是无法判断出具体导致内存泄露的类或者方法的,可以使用 List objects 或 Merge Shortest Paths to GC roots–>exclude all phantom/weak/soft etc.reference(排除所有虚弱软引用)–>查看剩余未被回收的强引用对象占用原因 等功能继续钻取数据。如果Histogram视图展示的数量多的实例对象不是基础类型,是有嫌疑的某个类,如项目代码中的bean类型,那么就要重点关注了。
打开方式:
- Overview > Actions > The Histogram
- 工具栏 > Histogram
Dominator Tree
支配树,分析对象的引用关系。 对象内存占用&占比
dominate_tree -> 对象调用堆栈树-查找内存占用最高对象(Retained Heap倒叙排序) -> Paths to GC Roots -> exclude all phantom/weak/soft etc.reference(排除所有虚弱软引用) -查找GC Root线程-> 定位未释放内存代码段
Actions > dominator_tree (查看堆中内存占用最高的对象的线程调用堆栈) -> 对象调用堆栈树-查找内存占用最高对象(Retained Heap倒叙排序) -> Paths to GC Roots -> exclude all phantom/weak/soft etc.reference (排除所有虚弱软引用) -查找GC Root线程 -> 查找未释放的内存占用最高的代码逻辑段(很可能是产生内存溢出代码)
Leak Suspects
打开方式:
- Overview > Reports > Leak Suspects
- 工具栏 > Run Expect System Test > Leak Suspects
自动分析内存泄露可疑点,给出一份可疑分析报告。MAT将报告内容压缩打包到一个zip文件,并放在原始堆转储文件的目录下,一般命名为xxx_Leak_Suspects.zip
,xxx是dump文件名,zip包文件很小,方便分发共享,请教他人协助排查内存泄露问题;报告是HTML文件。
以饼状图给出可疑的内存泄漏点,Details明细
OQL
Object Query Language,类似于SQL,能够用来查询当前内存中满足指定条件的所有对象。
Thread Overview
可看到:线程对象/线程栈信息、线程名、Shallow Heap、Retained Heap、类加载器、是否Daemon线程等信息。结合左侧的对象属性区域,可以更方便的看清线程中对象的具体情况。
打开方式:工具栏
Query Browser
对比dump堆栈文件
复杂的内存泄漏情况,需要通过对比hpof文件来进行分析。
生成第一个hpof文件,进行一段时间操作,再生成第二个hpof文件。用MAT打开这两个hpof文件。
将第一个和第二个hpof文件的Dominator Tree或者Histogram添加到Compare Basket中,之后选中2个文件对比即可
2个dump文件对比寻找波动差异(关注差异波动大的对象)
参考
- MAT open heap dump file failed with java heap space
- Eclipse Memory Analyzer Tool Help
- 使用MAT分析线上故障(一) - 视图&功能篇