一. 简介
Eclipse Memory Analyzer是一个快速且功能丰富的Java堆分析器,可帮助您查找内存泄漏并减少内存消耗。使用Memory Analyzer分析具有数亿个对象的高效堆转储,快速计算对象的保留大小,查看谁阻止垃圾收集器收集对象,运行报告以自动提取泄漏嫌疑者。
通过MAT工具,可以做以下几方面的事儿:
- 找到最大的对象,因为 MAT 提供了合理的累积大小(retained size)
- 探索对象图,包括入站和出站引用
- 计算从GC Roots 到有趣对象的路径
- 查找内存浪费,如冗余 String 对象、空集合对象等...
二.下载地址
https://www.eclipse.org/mat/downloads.php
目前绝大多数开发工具都已经使用IDEA了,因此大家下载独立的MAT即可,注意:独立的MAT运行需要在JDK11及以上的环境。
三.Heap Dump
首先了解下Heap Dump,它也叫堆转储文件,是java进程在某个时间内的快照。它在触发快照的时候保存了很多信息:java对象和类信息。通常情况下,在写入堆转储之前会触发完整的 GC,因此它包含有关剩余对象的信息。
Memory Analyzer 能够处理来自各种平台的 HPROF 二进制堆转储、IBM 系统转储(旧版本需要预处理)和 IBM 便携式堆转储 (PHD)。
在Heap Dump中能得到的信息包括:(以下摘自官网)
Typical information which can be found in heap dumps (depending on the heap dump type):
All Objects
Class, fields, primitive values and references
All Classes
Classloader, name, super class, static fields
Garbage Collection Roots
Objects defined to be reachable by the JVM
Thread Stacks and Local Variables
The call-stacks of threads at the moment of the snapshot, and per-frame information about local objects
这里给大家翻译一下为:
可以在堆转储中找到的典型信息(取决于堆转储类型):
所有对象
类、字段、原始值和引用All Classes
类加载器、名称、超类、静态字段
垃圾收集根对象GC Roots
定义为可由 JVM 访问的对象(比如:局部变量和类静态变量)线程栈和局部变量
快照时刻线程的调用堆栈,以及有关本地对象的每帧信息
四.怎样获取Dump
Dump文件的格式为:HPROF,内存分析器可以处理HPROF 二进制格式的堆转储
那么我们可以从以下几方面来获取Dump文件:
Non-interactive 被动获取:
通过OOM获取,即在OutOfMemoryError后获取一份HPROF二进制Heap Dump文件,可以在jvm里添加参数:(除非真正发生 OOM,否则不涉及任何开销)
-XX:+HeapDumpOnOutOfMemoryError
这个参数对于生产系统来说是必须的,因为它通常是进一步分析内存泄露问题的唯一方法。
默认情况下,堆转储将在 JVM 的“当前目录”中生成。它可以使用-XX:HeapDumpPath=显式重定向,例如-XX:HeapDumpPath=/disk2/dumps。请注意,转储文件可能很大,可达千兆字节,因此请确保目标文件系统有足够的空间。
Interactive 主动获取
在虚拟机中添加参数如下,然后在Ctrl+Break组合键即可获取一份Heap Dump:
-XX:+HeapDumpOnCtrlBreak
通过 jmap 工具生成,在命令行中输入:
jmap -dump:format=b file=<文件名XX.hprof> <pid>
Sun JConsole:启动 jconsole.exe 并在 HotSpotDiagnostic MBean 上调用操作 dumpHeap()
使用Memory Analyzer Tools的File -> Acquire Heap Dump功能
图形化界面操作,直接选择想要dump的pid进程,设置存放文件的路径即可。
五.MAT使用
准备一份dump文件,通过MAT工具进行打开,在选好文件后,会让你选一下报告类型,默认是内存泄漏探测报告,没有什么特殊情况下,选它就可以了:
内存泄漏可疑点(Leak Suspects)
进入MAT后会展示内存泄漏可疑点在内存中的分布情况,还会列出具体的类,如果是自己的代码,基本上一眼大概就能定位到某些业务,如果还不能肯定,也没关系,接下来还有很多功能可以辅助分析
上图中Problem Suspect1 即代表可能出现内存泄露的问题分析:
The thread java.lang.Thread @ 0xff626d38 main keeps local variables with total size 7,293,920 (89.25%) bytes.
在线程的main中持续引用本地变量达到了 7.3M的内存,占用堆内存89.25%
The memory is accumulated in one instance of java.lang.Object[], loaded by , which occupies 7,292,936 (89.24%) bytes.
内存累积在一个“java.lang.Object[]”实例中,由“”加载,占用7,292,936 (89.24%)字节。说明的非常清晰,这个数组占据了大量的内存。
那么这个数组里到底是什么东西呢?
在上图中的左下角大家看到有一个 Details,大家点进去即可看到详细的说明:
通过Details我们可以看到,在主线程的下方引用了一个 java.util.ArrayList, 这里面是一个java.lang.Object[]数组,通过这里我们既可以清楚到底是什么对象占用了过大的内存,所以MAT分析内存是非常方便的。
六.追踪线程执行堆栈,找到问题代码
一旦发现在某个线程执行过程中创建了大量对象之后,就可以尝试找这个线程到底执行了哪些代码才创建了这些对象,我们可以点击下图中的“See stacktrace”:
点击进去后即准确找到了发生问题的代码行数:
这里我们贴出对应的源代码,其实非常简单,如下:
代码问题就出现在了第10行,list添加元素这儿。
七. Histogram
刚才我们是在Leak Suspects中进行分析的,另外我们还可以打开一个非常常用的工具:Histogram
点击进去:
这里每一列的参数做一个解释:
- Class Name : 类名称,java类名
- Objects : 类的对象的数量,这个对象被创建了多少个
- Shallow Heap :一个对象内存的消耗大小,不包含对其他对象的引用
- Retained Heap :是shallow Heap的总和,也就是该对象被GC之后所能回收到内存的总和
可以看到Object[]这个数组就占据了7.4MB.
在某一项上右键打开菜单选择 list objects ->with incoming refs 将列出该类的实例:
大家可以发现通过这种方式也是能直观找到我们的大对象到底是什么。
好了,相信通过本篇文章的讲解,大家对于MAT工具的使用有了一定的理解和参考,后续遇到内存泄露、内存卡顿、大对象数据分析都可以直接通过MAT精准确定,更好的优化我们的项目。