MAT从入门到精通
- 概述
- 安装
- mac安装
- 指定jdk
- 配置内存
- 使用
- 配置
- 获取dump文件
- Overview下功能解释
- Histogram
- Dominator Tree
- Leak Suspects
- Overview功能说明结尾
- Thread_Overview
- OQL
- Heap Dump Overview
- Find Object by address
概述
尽管JVM提供了自动内存管理的机制,试图降低程序员的开发门槛,确实也实现了这一目标,在日常开发中,我们一般都不需要关心对象的内存释放。JVM大部分都是使用trace算法来判断一个对象是否该被回收,那么JVM只能回收那些从gc roots不可达的对象。
如果我们在使用某些大的对象、集合对象或者一些三方包里的资源,忘记及时释放资源的话,还是会造成JVM的内存泄漏或内存浪费的问题。因此,如果想成为更高阶的Java开发工程师,我们需要了解常见的问题排查的办法和工具,这个系列的文章,准备介绍一个用来做JVM堆内存分析的工具——MAT(Memory Aanlysis Tool)。
MAT的官网在:https://www.eclipse.org/mat/,可以看下它的介绍——MAT是一款高性能、具备丰富功能的Java堆内存分析工具,可以用来排查内存泄漏和内存浪费的问题。
安装
mac安装
MAT 支持两种安装方式,一种是"单机版“的,也就是说用户不必安装 Eclipse IDE 环境,MAT 作为一个独立的 Eclipse RCP 应用运行;另一种是”集成版“的,也就是说 MAT 也可以作为 Eclipse IDE 的一部分,和现有的开发平台集成。
这里我们考虑独立安装,在观望的下载页面,选择mac os版本的安装文件下载即可。
指定jdk
官网中给了描述
The minimum Java version required to run the stand-alone version of Memory Analyzer is Java 11. See JRE/JDK Sources.
需要的jdk得 >=11
这里选择 azul的jdk18 进行下载(注意m1的芯片选择arm 64 非m1的 x86_64) https://www.azul.com/downloads/?version=java-18-sts&os=macos&architecture=arm-64-bit&package=jdk
对mat进行配置一下
创建workspace
cd /Applications/mat.app/Contents/MacOS
mkdir workspace
修改 Info.plist文件
cd /Applications/mat.app/Contents
vim ./Info.plist
添加jdk和 工作空间配置
<!-- 指定启动使用的JVM路径 -->
<string>-vm</string>
<!-- /usr/local/develop/java/zulu-18.jdk/bin/java 需要修改为自己的配置 -->
<string>/usr/local/develop/java/zulu-18.jdk/bin/java</string>
<!-- 指定启动的工作空间 -->
<string>-data</string>
<string>/Applications/mat.app/Contents/MacOS/workspace</string>
配置内存
我的电脑是8C16G的,那理论上分析10G的堆文件没问题,但是MAT默认的配置没有这么大,需要在/Applications/mat.app/Contents/Eclipse/MemoryAnalyzer.ini文件中进行修改。如下图所示,我将我的MAT自己的运行时堆内存配置成了6G。
使用
配置
MAT的配置页面可以从Window——>Preferences找到,如下图所示。
MAT的一般配置有几个选项
- Keep unreachable objects:如果勾选这个,则在分析的时候会包含dump文件中的不可达对象;
- Hide the getting started wizard:隐藏分析完成后的首页,控制是否要展示一个对话框,用来展示内存泄漏分析、消耗最多内存的对象排序。
- Hide popup query help:隐藏弹出查询帮助,除非用户通过F1或Help按钮查询帮助。
- Hide Welcome screen on launch:隐藏启动时候的欢迎界面
- Bytes Display:设置分析结果中内存大小的展示单位
可以看出,MAT不仅支持HPROF文件的分析,还支持DTFJ文件的分析。一般sun公司系列的JVM生成的dump文件都是HPROF格式的,IBM的JVM生成的dump文件时DTFJ格式的。
获取dump文件
一个通过连接本地程序获取,本地测试使用
线上程序往往都是生产dump文件后直接打开相应文件
打开Mat后File>OpenHeapDump打开一个对应的dump文件后,此时对应的打开后结果如图所示
Overview下功能解释
Overview页签下分别包含了:Actions,Reports,Step By Step 三大块功能;每一块功能下的子集所对应的作用分别是:
-
Actions:
-
Histogram 列出每个类所对应的对象个数,以及所占用的内存大小;
-
Dominator Tree 以占用总内存的百分比的方式来列举出所有的实例对象,注意这个地方是直接列举出的对应的对象而不是类,这个视图是用来发现大内存对象的
-
Top Consumers:按照类和包分组的方式展示出占用内存最大的一个对象
-
Duplicate Classes:检测由多个类加载器所加载的类信息(用来查找重复的类)
-
-
Reports:
-
Leak Suspects:通过MAT自动分析当前内存泄露的主要原因
-
Top Components:Top组件,列出大于总堆1%的组件的报告
-
-
Step By Step:
- Component Report:组件报告,分析属于公共根包或类加载器的对象;
上述所有被标注加粗的部分,是内存溢出dump分析时较为常用的功能点也是下面主要讲解的内容。
Histogram
通过Histogram 列出每个类所对应的对象个数,以及所占用的内存大小;
此处选中一个ClassName单击后,通过左上角Inspector可以看到当前类的回收情况,内存地址,等
补充解释:
-
字段一:表示当前类所对应的对象数量
-
字段二:Shallow Size是对象本身占据的内存的大小,不包含其引用的对象。对于常规对象(非数组)的Shallow Size由其成员变量的数量和类型来定,而数组的ShallowSize由数组类型和数组长度来决定,它为数组元素大小的总和;
-
字段三:Retained Size=当前对象大小+当前对象可直接或间接引用到的对象的大小总和。(间接引用的含义:A->B->C,C就是间接引用) ,并且排除被GC Roots直接或者间接引用的对象;
关于红框内的Statics,Attributes,Classhierarchy,Value则分别表示当前类的静态变量,属性,当前类的层次结构图,以及当前类所对应的值Value;
注意:当前Histogram的列属性:ClassName,Objects,ShallowHeap,RetainedHeap这几个列属性下面都是有提供一个输入框,通过该输入框可以进行相关类的检索,比如:在ClassName下输入一个正则.quark.那么则获取到所有包路径为quark的类信息;
Dominator Tree
以占用总内存的百分比的方式来列举出所有的实例对象,可以用来发现大内存对象;
如上图所示:可以看到ConcurrentHashMap@0x60191cfa8这个对象占据了98.92%的堆大小,所以基本就可以断定,当前项目之所以会down机的主要原因是,ConcurrentHashMap溢出所导致的问题;
那么当我们需要查看,当前该ConcurrentHashMap@0x60191cfa8对象都引用了那些数据,以及当前该对象是被那几个对象所引用的,如何查看?
鼠标在当前所要查看的对象右键,点击List Objects可以看到分别提供了:with outgoing references(查看当前该对象的所有的引用信息) 和 with incoming references(查看当前该对象是被那几个对象所引用的) ;
- 在 Dominator tree 中展开树状图,可以查看支配关系路径(与 outgoing reference 的区别是:如果 X 支配 Y,则 X 释放后 Y必然可释放;如果仅仅是 X 引用 Y,可能仍有其他对象引用 Y,X 释放后 Y 仍不能释放,所以 Dominator tree 去除了 incoming reference 中大量的冗余信息)。
- 有些情况下可能并没有支配起点对象的 Retained Heap 占用很大内存(比如 class X 有100个对象,每个对象的 Retained Heap 是10M,则 class X 所有对象实际支配的内存是 1G,但可能 Dominator tree 的前20个都是其他class 的对象),这时可以按 class、package、class loader 做聚合,进而定位目标。
Leak Suspects
通过MAT自动分析当前内存泄露的主要原因
可以看到,当前MAT所给出内存泄露的主要原因是:当前实例java.util.concurrent.ConcurrentHashMap被加载自system class loader,共占用了 98.92%的堆内存,这个实例被引用自org.apache.ignite.internal.processors.cache.binary.CacheObjectBinaryProcessorImpl并且这个CacheObjectBinaryProcessorImpl这个对象是加载自LaunchedURLClassLoader这个类加载器;
并且还给出了所对应的主要关键词是:
java.util.concurrent.ConcurrentHashMap$Node[]
java.util.concurrent.ConcurrentHashMap
org.springframework.boot.loader.LaunchedURLClassLoader @ 0x6000a6860
基本上可以说是很详细了,一语中的,如果想要查看明细,可以直接点击detail,里面有更详细的说明,如下图所示:
上图分别说明了到该内存泄漏的对象的最快路径,也就是列出了当前ConcurrentHashMapConcurrentHashMap@0x60191cfa8这个对象所对应的被引用关系:可以看到当前引起内存泄漏的ConcurrentHashMap被CacheObjectBinaryProcessorImpl@0x60191cea8这个对象的metadataLocCache这个属性所引用,而CacheObjectBinaryProcessorImpl这个对象又被GridKernalContextImpl @ 0x601821bf8这个对象的cacheObjeProc这个属性所引用,以此递推;
除此之外,还有以下三个被隐藏的信息,点击即可查看明细:
Accumulated Objects in Dominator Tree (主控树中的累积对象),Accumulated Objects by Class in Dominator Tree(主控树中的按类累积对象 ,All Accumulated Objects by Class (按类列出所有的累积对象)
Overview功能说明结尾
通过上述的解释应该对当前Overview下的功能使用已经有了一个大概的了解,需要注意的是,Histogram 以及Dominator Tree时所主要提及的Shallow Size以及Retained Size以及在所列出的对象上右键查看引用关系,GCROOTS,以及左上角所展示的属性明细等功能 是适用于所有的功能模块的,后续不再赘述;
一级导航栏功能说明
查看完上述关于Overview中的功能说明后,此处再来看一下Overview中不包含的一些功能
Thread_Overview
如下图所示,点击一级导航栏的第5个图标,可以用来查看当前进程dump时的所有线程的堆栈信息,通过分析下面所对应的堆栈信息,可以很快速的定位到对应的线程所执行的方法等层级关系,以此来定位对应的异常问题;
OQL
用于查询Java堆的类SQL查询语言
Heap Dump Overview
点击一级导航栏的第6个图标的下拉框下的 Heap Dump Overview,可以查看全局的内存占用信息
Find Object by address
查看指定内存地址所对应的对象信息;
待整理:
https://blog.csdn.net/x275920/article/details/123991656