1. 使用背景

上一篇文章中,我们介绍了可以使用jmap命令对当前运行的系统生成一份dump快照,并且可以使用 jhat启动一个web服务器进行快速查看(但是通过这个服务器查看的java对象分布也比较简略)。

在这篇文章里,再介绍一个更加实用和功能更加强大的内存分析工具:MAT。

除了我们主动生成dump快照来查看内存分布之外,在系统发生OOM的时候,如果你配置了 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/...这两个jvm参数,在程序崩溃前jvm会自己生成一份名为 java_pidxxxxx.hprof 的dump快照,保存OOM时系统的运行情况,提供给我们进行分析和排查问题。

所以,线上系统一定要打开这两个jvm参数,否则如果发生了OOM等问题很难去定位;
分析和定位OOM问题,也是MAT最大的功能

2. MAT 介绍及使用

MAT(Memory Analyzer Tool),一款功能强大的内存分析工具,它有Eclipse的插件,也有自己单独的安装包。

在分析大内存的dump文件时,可以非常直观的看到各个对象在堆空间中所占用的内存大小、类实例数量、对象引用关系、利用OQL对象查询,以及可以很方便的找出对象GC Roots的相关信息;

当然它最强大的功能还是 能够快速为开发人员生成内存泄露报表,方便定位问题和分析问题

2.1 MAT 安装

下载地址:https://www.eclipse.org/mat/downloads.php

安装好MAT之后,在安装目录中可以看见有一个文件名为 MemoryAnalyzer.ini 的文件,它的内容如下:

-startup
plugins/org.eclipse.equinox.launcher_1.5.0.v20180512-1130.jar
--launcher.library
plugins/org.eclipse.equinox.launcher.win32.win32.x86_64_1.1.700.v20180518-1200
-vmargs
-Xmx1024m

文件中通过 -Xmx1024m 参数指定了MAT本身的堆内存大小为1G,但是我们dump出来的内存快照通常会很大,比如几个G;
所以我们需要在启动MAT之前,先把这个参数修改为4个G,或者8个G:-Xmx4096m/-Xmx8192m

2.2 MAT 使用

在这里以我们线上系统真实出现过的一次问题的排查及定位过程,来介绍MAT的使用步骤。

2.2.1 现象

某天我们线上系统在运行着的时候,突然被前端告知所有接口都无法请求了,听到这个消息我就猜测应该是应用进程没了,很大可能是发生了OOM。

所以先检查日志:
 
果然看到日志中出现了OOM,并且我们的系统都会打开 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/...这两个jvm开关,所以就再到指定目录去查看:
 
也看到了在OOM时生成的dump日志,这时我们就需要把这个日志拿出来进行分析。

2.2.2 分析定位

首先使用 MAT 打开这个dump文件:
 

2.2.2.1 查看 overview

 
可以看到,线程 TaskThread @ 0xc07b8e68 http-nio-8080-exec-7中的对象占用了 525M的内存,占比50%左右了(因为系统在开发阶段,所以我们只配置了1G的堆大小,在无压力情况下正常是够用的)。

2.2.2.2 查看 Actions -> Dominator_Tree

Dominator_Tree(支配树)可以直观地反映一个对象本身及它持有引用的对象所占的内存大小和比例;

  • Shallow Heap:对象本身所占的内存大小;
  • Retained Heap:对象的 retained set 所包含对象所占内存的总大小;(retained set 是这个对象本身和它持有引用的对象和这些对象的 retained set 所占内存大小的总和)
  • Percentage:占堆内存的比例;

 
可以看到,这里占用内存最多的,也正是上面 overview中占比 50%左右的 TaskThread @ 0xc07b8e68 http-nio-8080-exec-7线程;

点开这个线程,继续点开 ArrayList:
 
可以看到,在一个ArrayList中生成了 204201个 ServerDto对象,它们占用了 25%左右的内存空间;(这里有经验的一看肯定就知道了,正常运行的系统不应该会在无压力下短时间生成这么多对象的)

ArrayList占用了25%的内存空间,再继续往下看,看见有一个char,它还占用了 7%左右的内存空间:
 
然后我复制出这个char,是如下内容:

INSERT INTO monitor_group_target_server
        (monitor_group_id,
        ci_no,
        reg_time,
        register)
        VALUES
          
            (#{__frch_item_0.monitorGroupId},
           {__frch_item_0.ciNo},
           {__frch_item_0.regTime},
           {__frch_item_0.register})
         , 
            (#{__frch_item_1.monitorGroupId},
           {__frch_item_1.ciNo},
           {__frch_item_1.regTime},
           {__frch_item_1.register})
         , 
            (#{__frch_item_2.monitorGroupId},
           {__frch_item_2.ciNo},
           {__frch_item_2.regTime},
           {__frch_item_2.register})
         , 
            (#{__frch_item_3.monitorGroupId},
           {__frch_item_3.ciNo},
           {__frch_item_3.regTime},
           {__frch_item_3.register})
......    

可以看出来,这里是用于批量插入的拼接的sql语句,他们一共有 204201 条,跟上面的 ServerDto对象个数一致;

再往下看,还有一个 ParameterMappingTokenHandler对象 也占用了7%左右的内存空间:
 
打开这个对象,看到了里面是80w个左右的 ParameterMapping对象 ,看到这些对象,并结合上面的char中的INSERT语句,使用过mybatis的同学应该可以猜到了,这里要做的事情就是:通过拼接sql语句,使用mybatis的批量插入。

再结合存在的 204201个ServerDto对象,可以大概猜测就是:这里有一个什么查询语句 查出了20w个左右的 ServerDto对象,并且根据它的某些属性去拼接sql来进行批量插入;它们已经占用了很大的内存了,再加上系统中其他一些对象占用的内存,就发生了OOM;

当然到这里只是猜测,那我们继续排查。

2.2.2.3 查看 Reports -> Leak Suspects

 
点开Leak Suspects(泄漏疑点)之后,可以看到这里也有 overview中的饼状图的,不过它不是我们关注的重点;

在这里我们需要重点关注的是:Problem Suspect 1、Problem Suspect 2等,它们的意思是在生成这个dump快照的时候,系统中可能存在的 问题点,分别列了 1,2,3,4…

这也正是MAT功能的强大所在,它可以为我们自动生成分析的报表和一些统计信息

继续点See stacktrace,可以看到当前的线程栈情况:
 

在这里就可以通过线程的执行栈情况,定义到具体是你代码中什么地方出现了问题:
 
快捷方法:直接通过你应用的包名进行搜索,快速定位;

2.2.3 解决问题

在上面已经通过搜索,找到了代码中具体的类和方法,然后我们就可以直接对代码进行检查了;

这里代码检查的结果是:由于程序的控制问题,导致一个查询sql中没有带上where条件,就查出了全部20w个左右的 ServerDto对象;然后又使用ServerDto对象中的某些字段去拼接sql来进行另外一张表的批量插入,所以也拼接了20w条左右的sql语句,产生了80w个左右的 ParameterMapping对象…

所以通过修复代码中的条件控制,就可以解决这个导致发生OOM的问题了。

2.3 总结

当然,MAT也不止可以在OOM时分析dump文件;如果你的应用在运行过程中发现内存情况不正常,或者你自己想要去了解一下你的应用运行过程中的对象分布情况等,都可以通过jmap手动导出 dump文件再使用MAT进行分析。

另外,MAT还有一些强大的功能,例如:通过OQL(Object Query Language,类似于SQL语句的查询语言)来查询当前内存中满足指定条件的对象、查看对象的引用关系和GC Roots、对比dump堆栈文件等;

但是更为日常的使用,就是通过上面介绍的步骤,使用MAT来分析和定位应用的OOM问题或者对象的分布情况。