目录
- 一、前言
- 1.1 简介
- 1.2 下载安装
- 二、使用
- 2.1 Histogram(直方图)
- 2.2 Dominator Tree(支配树)
- 2.3 Top Consumers
- 2.4 Duplicate Classes(重复类)
- 2.5 Suspects(疑似泄露点)
- 三、问题总结
- 3.1 分析源码
- 3.2 解决办法
一、前言
1.1 简介
Eclipse Memory Analyzer 是一个快速且功能丰富的Java 堆分析器,可帮助我们发现内存泄漏并减少内存消耗。简称MAT。
1.2 下载安装
下载时需要注意自己的jdk版本,选择对应版本的MAT下载。下载完成,解压。就可以直接打开,免安装。
二、使用
- 双击打开 MemoryAnalyzer.exe
- 左上角File->Open Heap Dump 选择事先准备好的dump文件(hprof文件)
2.1 Histogram(直方图)
直方图提供了每个类对象的统计
- Class Name 表示对象类型
- Objects:类的对象的数量。
- Shallow Heap:就是对象本身占用内存的大小,不包含对其他对象的引用,也就是对象头加成员变量(不是成员变量的值)的总和。
- Retained Heap:是该对象自己的shallow size,加上从该对象能直接或间接访问到对象的shallow size之和。换句话说,retained size是该对象被GC之后所能回收到内存的总和。
我们可以点击Retained Heap让他们按照Retained Heap的大小排序 。从上图可以看出前面的几个对象引用的大部分资源,问题很可能就出现在这些对象上。
2.2 Dominator Tree(支配树)
支配树(Dominator Tree),提供程序中最占内存的对象 。
- Class Name 类名
- Shallow Heap 浅堆
- Retained Heap 深堆
- Percentage 百分比
上面的图片可以发现DruidPooledPreparedStatement占用了大部分资源。我们这个时候其实可以直接选择对象右击选择List Objects -> with outgoing references 、 with incoming references 或 Path To GC Roots 看对象的引用链路排查问题。
2.3 Top Consumers
Top Consumers 显示了内存中最大的对象有哪些,他们对应的类是哪些,类加载器classloader是哪些。有些时候,我们在这里就可以看到代码泄露的位置。
从上面的图片可以看出来DruidPooledPreparedStatement占用了大部分资源(35.7M)。
2.4 Duplicate Classes(重复类)
Duplicate Classes 检测由多个类加载器加载的类。
2.5 Suspects(疑似泄露点)
提供了MAT诊断的疑似泄露点信息
点击上图的Details信息可以查看详情,如下:
选中一个占用内存较高的对象右击->List objects 然后可以选择with outgoing references 和with incoming references。
- with outgoing references 是该对象引用了那些对象实例
- with incoming references 是该对象被谁引用
我们还可以进一步查询对象到GC Root的全链路,如图选择去除弱引用
疑似泄露点也推断是DruidPooledPreparedStatement造成内存泄露。通过List objects -> with outgoing references 可以看到DruidPooledPreparedStatement的resultSetTrace持有很多的DruidPooledResultSet无法释放。通过with incoming references 可以看到是FilterMap类里面的queryPs占用了很多资源。
三、问题总结
3.1 分析源码
我们找到了代码的FormatMap 发现执行了 DruidPooledPreparedStatement的executeQuery方法。在执行executeQuery方法的时候会把每次查询的结果放到resultSetTrace字段里。因为我们没有清理所有DruidPooledResultSet堆积,导致内存无法回收,最终OOM。
3.2 解决办法
DruidPooledPreparedStatement的clearResultSet方法会清理resultSetTrace。但是这个方法是protected的我们不能调用。
最后发现是DruidPooledPreparedStatement的close方法会调用clearResultSet方法清理resultSetTrace。