前言
前段时间,运维反馈生产环境翻译服务某个节点触发内存告警了。运维在重启节点之前,生成了dump快照,这里介绍下如何使用MAT内存分析工具来排查服务内存高占用问题。
MAT简介
MAT是Memory Analyzer的简称,它是一款功能强大的Java堆内存分析器。可以用于查找内存泄露以及查看内存消耗情况。MAT是基于Eclipse开发的,是一款免费的性能分析工具。读者可以在Eclipse Memory Analyzer Open Source Project | The Eclipse Foundation下载并使用MAT。
问题排查经过
MAT有免安装版本,我在自己电脑windows上直接打开就行
这里需要注意一下,运维发过来的dump文件大小有8G左右,MAT的默认内存是1024M,直接导入这个8G的dump文件,工具解析了一会就报错了,需要修改下MAT的配置文件,把内存配置调大
这里直接把1024改成了6024,由于在本地电脑上解析,解析时间有点漫长....,条件允许的话,完全可以在开发或者联调环境Linux服务器上用MAT(有Linux版本)解析,解析完成之后页面如下
很明显,我们看到了一个大对象集合
上图可以看到存在一个大的对象集合,并且在图的左下角,有对应的线程名字,我们可以根据这个线程名到日志中取查询下具体哪一个请求导致了这个大对象集合。
再根据请求对应的traceId,找到请求链路,以及对应的业务数据
后来发现是某个PPT文档翻译,导致创建了一个大的对象集合,最终占用了接近5G的内存。问题到这里,内存占用的原因就算是找到了,具体PPT翻译的问题,交给开发PPT翻译的同时排查即可。
MAT的其他功能介绍
查看对应线程详情
如上图所示,可以点击大对象,查找对应的线程详情
还有表格形式
当然,这里查看Java层面的应用线程,对于虚拟机的系统线程是无法显示的。通过线程的堆栈,还可以查看局部变量的信息。
浅堆和深堆
浅堆(Shallow Heap)和深堆(Retained Heap)是两个非常重要的概念,它们分别表示一个对象结构所占用的内存大小和一个对象被GC回收后,可以真实释放的内存大小。浅堆(Shallow Heap)是指一个对象所消耗的内存。在32位系统中,一个对象引用会占据4个字节,一个int类型会占据4个字节,long型变量会占据8个字节,每个对象头需要占用8个字节。根据堆快照格式不同,对象的大小可能会向8字节进行对齐。以String对象为例,如下图所示,显示了String对象的几个属性。
- String
- value:char[]
- offset:int
- count:int
- hash:int
3个int值共占12字节,对象引用占用4字节,对象头8字节,合计24字节。浅堆的大小只与对象的结构有关,与对象的实际内容无关。也就是说,无论字符串的长度有多少,内容是什么,浅堆的大小始终是24字节。
深堆(Retained Heap)的概念略微复杂。要理解深堆,首先需要了解保留集(Retained Set)。对象A的保留集指当对象A被垃圾回收后,可以被释放的所有的对象集合(包括对象A本身),即对象A的保留集可以被认为是只能通过对象A被直接或间接访问到的所有对象的集合。通俗地说,就是指仅被对象A所持有的对象的集合。深堆是指对象的保留集中所有的对象的浅堆大小之和。
注意:浅堆指对象本身占用的内存,不包括其内部引用对象的大小。一个对象的深堆指只能通过该对象访问到的(直接或间接)所有对象的浅堆之和,即对象被回收后,可以释放的真实空间。
另外一个常用的概念是对象的实际大小。这里,对象的实际大小定义为一个对象所能触及的所有对象的浅堆大小之和,也就是通常意义上我们说的对象大小。与深堆相比,似乎这个在日常开发中更为直观和被人接受,但实际上,这个概念和垃圾回收无关。
如图下图所示,显示了一个简单的对象引用关系图,对象A引用了C和D,对象B引用了C和E。那么对象A的浅堆大小只是A本身,不含C和D,而A的实际大小为A、C、D三者之和。而A的深堆大小为A与D之和,由于对象C还可以通过对象B访问到,因此不在对象A的深堆范围内。
在MAT中查看对象浅堆和深堆的大小:
选中对象,单击右键,在弹出的菜单中都有 Show Retained Set 命令,它可用于显示指定类或者对象的保留集。
内存分析报告
可以生成内存分析报告,报告中检测出一个异常
等等,MAT功能还是挺多的,可以多玩玩。