Exlipse MAT 是一款强大的 Java 堆内存分析工具,我们可以通过该工具实现对 Java 堆内存的分析。
官网。实现查找内存泄漏以及查看内存消耗的情况。
MAT 使用
首先去官网下载对应的软件压缩包,下载地址。MAT 提供了多种平台的软件包支持,包括 Windows , Mac OSX ,Linux 等,可以根据你当前的系统进行下载。由于我使用的是 Win10,因此下载了 Windows(x86_64)版本的。
需要注意的是新版的 MAT 需要系统的 Java 版本在 11 及以上,如果你的版本不够,则需要重新配置一下环境中的 Java 版本。
对于老版的 MAT,是作为一个 Eclipse IDE 插件提供的,新版的 MAT 增加了独立的 MAT 支持,使得用户不需要额外安装 Eclipse 也可以使用 MAT。
初识 MAT
在分析堆快照之前,首先需要导出应用程序的堆快照。我们可以使用 jmap
、jconsole
和Java Visual VM
实现堆快照的导出。除此之外使用 MAT 也能够实现堆快照的导出。
单击左上角的File
菜单下的Accquire Heap Dump
选项,选择对应正在运行的 Java 进程即可。
解析 dump 出的堆内存快照
除了直接在 MAT 中获取正在运行的应用程序的堆快照之外,也可以通过 “Open Heap Dump” 来打开一个本地的堆快照。
建议将堆内存快照文件单独放在一个文件夹下,因为 MAT 在分析堆内存快照的时候会在对应目录下生成一些索引文件,因此将堆内存快照单独放在一个文件夹下便于查看。
在打开堆快照后,会出现如下的界面:
可以根据界面的指引选择你想用的功能即可。
浅堆(Shallow heap)和深堆(Retained Heap)
在 MAT 中会发现有两列分别描述了对象的浅堆(Shallow heap) 和深堆(Retained Heap)。这两列都是用于描述对象的大小,以 java.lang.String
为例:
可以发现这里 java.lang.String
的浅堆大小都是 24 字节,而深堆大小与具体对象有关,各不相同。
- 浅堆(Shallow Heap) 用于描述一个对象结构所占用的内存大小
- 深堆(Retained Heap) 用于描述一个对象被 GC 后,可以释放的真实内存的大小。
浅堆(Shallow Heap)
浅堆是指一个对象所消耗的内存。
浅堆的大小只与对象的结构有关,与对象的实际内容无关,也就是说在一个 Java 进程块中,相同类型的所有对象,浅堆的大小是固定不变的。
在32 位操作系统 和 64 位操作系统上,一个对象所占用的内存大小也各不相同,但基本数据类型所占用的内存大小是不变的,如下:
基本数据类型 | 所占内存大小(字节) |
---|---|
byte | 1 |
short | 2 |
int | 4 |
long | 8 |
float | 4 |
double | 8 |
char | 2 |
boolean | 1 |
有区别的是引用类型,主要是和 CPU 的字长有关。
- 在 32 位操作系统上,一个对象引用占 4 个字节(4x8=32)
- 在 64 为操作系统上,一个对象引用占 8 个字节(8x8=64)
引用类型实际上就是一个地址指针。在 32 位操作系统上,占用 4字节(4x8=32),在 64位操作系统上,占用 8 字节(8x8=64)。
在 JDK1.6 之后,默认开启了指针压缩(
-XX:UseCompressedOops
),则64位操作系统上地址指针占用 4字节。
而对于对象,对象头在不同的操作系统上大小也不同。
- 在 32 位操作系统上,一个对象头大小占 8 个字节(4字节 Markword + 4字节 类型指针)
- 在 64 位操作系统上,一个对象头大小占 16 个字节(8字节 MarkWord + 8字节 类型指针)
在 64 为操作系统上,如果开启了指针压缩,则对象头的大小为 12 字节(8字节 MarkWord + 4 字节类型指针)
现在我们来尝试分析一下 java.lang.String
类型的对象的浅堆为什么是 24 字节吧。
由于开启了指针压缩,
- 首先 java.lang.String 的对象头为
12
字节, - 包含一个 名为
hash
类型为int
的属性,占4
字节; - 包含一个 名为
value
类型为 引用类型(ref
) 的属性,占4
字节; - 在64 位操作系统中,为了和
8
字节对齐,会增加4
字节的对齐填充
总共占用为(12
+ 4
+ 4
+ 4
) = 24
字节。
深堆(Retianed Heap)
而深堆的计算就稍微有些复杂了,我们不仅需要计算该对象的大小,而且还需要计算该对象所包含的引用对象的大小才能够真正计算出该对象深堆的大小。
首先需要列出一个对象以及对象中所包含的引用:
使用 List objects
可以列出该类型的全部对象,其中with outgoing references
表示列出该对象所包含的对象,with incoming reference
表示列出其他包含该对象的对象。
有点绕口,这里举个例子:
对于 String(“zhangsan”) 来说,
User 对象包含了该 String,处于 “zhangsan” 的上游,那么该 User 对象是 “zhangsan” 的 incoming。
而 “zhangsan” 所包含的 char[8] 处于 “zhangsan” 的下游 ,则是 “zhangsan” 的 outgoing。
如果要计算深堆的话,我们需要将 String 对象的下游列出来,因此要选择 with outgoing reference
。
我们这里计算需要将该对象的浅堆加上该对象所包含引用对象的深堆相加。
比如这里 88 字节 = 24(Shallow Heap) + 64(char[24])
。
支配树视图(dominator tree
)
支配树视图可以体现对象实例间的支配关系。
注意:对象支配树中,某一个对象的子树,表示在该对象被回收后,也将被回收的对象的集合。
打印出的堆内存快照过大怎么办
可以使用 MAT 的命令行模式首先进行对内存的预分析.
打开命令行,执行如下命令:
ParseHeapDump.bat <path/to/heap_dump> org.eclipse.mat.api:suspects org.eclipse.mat.api:overview org.eclipse.mat.api:top_components
# 示例
ParseHeapDump.bat D:\Downloads\heap_dump\java.hprof org.eclipse.mat.api:suspects org.eclipse.mat.api:overview org.eclipse.mat.api:top_components
- 1
- 2
- 3
参数 | 说明 |
---|---|
org.eclipse.mat.api:suspects | 生成内存泄漏检查报告 |
org.eclipse.mat.api:overview | 生成内存分析报告 |
org.eclipse.mat.api:top_components | 生成大对象内存占用报告 |
在命令执行完成后,对应目录下,就会出现相对应的报告的压缩包:
我们可以直接解压后,通过浏览器浏览该报告,也可以使用 MAT 读取报告。
参考资料
Eclipse MAT内存分析
Eclipse MAT内存分析工具
jvm内存快照dump文件太大,怎么分析
JVM heap dump分析