目录

  • 一、前言
  • 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下载。下载完成,解压。就可以直接打开,免安装。

Memory Analyzer Tool下载指定版本_oom

二、使用

  • 双击打开 MemoryAnalyzer.exe
  • 左上角File->Open Heap Dump 选择事先准备好的dump文件(hprof文件)

2.1 Histogram(直方图)

直方图提供了每个类对象的统计

Memory Analyzer Tool下载指定版本_oom_02

  • Class Name 表示对象类型
  • Objects:类的对象的数量。
  • Shallow Heap:就是对象本身占用内存的大小,不包含对其他对象的引用,也就是对象头加成员变量(不是成员变量的值)的总和。
  • Retained Heap:是该对象自己的shallow size,加上从该对象能直接或间接访问到对象的shallow size之和。换句话说,retained size是该对象被GC之后所能回收到内存的总和。

我们可以点击Retained Heap让他们按照Retained Heap的大小排序 。从上图可以看出前面的几个对象引用的大部分资源,问题很可能就出现在这些对象上。

2.2 Dominator Tree(支配树)

支配树(Dominator Tree),提供程序中最占内存的对象 。

Memory Analyzer Tool下载指定版本_直方图_03

  • 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是哪些。有些时候,我们在这里就可以看到代码泄露的位置。

Memory Analyzer Tool下载指定版本_直方图_04


Memory Analyzer Tool下载指定版本_eclipse_05

从上面的图片可以看出来DruidPooledPreparedStatement占用了大部分资源(35.7M)。

2.4 Duplicate Classes(重复类)

Duplicate Classes 检测由多个类加载器加载的类。

Memory Analyzer Tool下载指定版本_oom_06

2.5 Suspects(疑似泄露点)

提供了MAT诊断的疑似泄露点信息

Memory Analyzer Tool下载指定版本_eclipse_07


点击上图的Details信息可以查看详情,如下:

Memory Analyzer Tool下载指定版本_eclipse_08


Memory Analyzer Tool下载指定版本_List_09

选中一个占用内存较高的对象右击->List objects 然后可以选择with outgoing references 和with incoming references。

  • with outgoing references 是该对象引用了那些对象实例
  • with incoming references 是该对象被谁引用

我们还可以进一步查询对象到GC Root的全链路,如图选择去除弱引用

Memory Analyzer Tool下载指定版本_List_10


Memory Analyzer Tool下载指定版本_java_11

疑似泄露点也推断是DruidPooledPreparedStatement造成内存泄露。通过List objects -> with outgoing references 可以看到DruidPooledPreparedStatement的resultSetTrace持有很多的DruidPooledResultSet无法释放。通过with incoming references 可以看到是FilterMap类里面的queryPs占用了很多资源。

三、问题总结

3.1 分析源码

我们找到了代码的FormatMap 发现执行了 DruidPooledPreparedStatement的executeQuery方法。在执行executeQuery方法的时候会把每次查询的结果放到resultSetTrace字段里。因为我们没有清理所有DruidPooledResultSet堆积,导致内存无法回收,最终OOM。

Memory Analyzer Tool下载指定版本_List_12

Memory Analyzer Tool下载指定版本_eclipse_13


Memory Analyzer Tool下载指定版本_eclipse_14

3.2 解决办法

DruidPooledPreparedStatement的clearResultSet方法会清理resultSetTrace。但是这个方法是protected的我们不能调用。

Memory Analyzer Tool下载指定版本_eclipse_15


最后发现是DruidPooledPreparedStatement的close方法会调用clearResultSet方法清理resultSetTrace。

Memory Analyzer Tool下载指定版本_eclipse_16