MAT全称Eclipse Memory Analyzer,是一个非常强大的的内存分析工具,可以帮助我们分析堆内存,找到内存泄露的地方,减少内存消耗。MAT除了可以作为Eclipse的插件使用。官方也提供了独立的安装版本。

1.安装

可以安装MAT的独立安装包,到官网https://www.eclipse.org/mat/下载对应平台即可。

对于M1芯片,我们需要另外安装x86的jdk,然后在修改MAT启动使用的jdk为x86并且11及以上版本的即可。虽然有一定性能损耗但是好歹能用。
image-1650267490354
在Contents/Eclipse/MemoryAnalyzer.ini文件中第一行添加一条,指明使用的jdk
image-1650267741137

-vm
/Library/Java/JavaVirtualMachines/jdk-11.0.13.jdk/Contents/Home/bin/java

2.页面介绍

在介绍MAT工具之前我们先介绍几个常用术语。在MAT工具中这几个术语经常出现:

  • Shallow Size: 对象自身占用的内存大小。
  • Retained Size: 对象本身的Shallow Size + 该对象直接或间接引用到的对象的Shallow Size。(也就是说Retained Size就是该对象被GC之后所能回收的内存的总和)
  • GC Roots: 是一组必须活跃的引用。GC会收集那些不是GC Roots且没有被GC roots引用的对象。基本思路就是通过一系列名为GC Roots 的对象作为起始点开始向下搜索。如果一个对象到GC Roots没有任何引用链相连时,则说明此对象不可用。换句话说就是能被遍历到的(可到达的)对象就被判定为存活,没有被遍历到的就自然被判定为死亡。
    image-1650267806177

2.1MAT结构介绍

image-1650267823723

区域一:Inspector区域

用于展示指定对象的详细信息(选定一个对象的时候),从上到下依次是:内存地址、加载器名称、包名、对象名称、对象所属类的父类、对象所属类的加载器对象对象的堆内存大小(shallow size)、保留大小(retained size)、gc roots信息。

区域二:Inspector区域下方的区域

展示对象的一些属性信息、类层级信息。

区域三:常用工具栏区域

常用工具按钮从左到右依次是:概览(Overview)、类直方图(Histogram)、支配树(Dominator Tree)、OQL查询、线程视图、报告相关、详细功能(提供了一些更细致的分析能力)。

区域四:功能视图区域

根据选择的功能不同,该区域显示对应功能的详细信息。比如我们想看Overview信息(点击区域三常用工具栏的第一个按钮)该区域就会展示heap dump Overview对应的信息。

2.2MAT视图

Overview视图

  • 使用MAT打开一个heap dump文件,解析完成后,默认就会进入Overview视图页面。
  • 工具栏中点击Overview按钮(区域三常用工具栏的第一个按钮)展示Overview对应的信息。
    image-1650267906636

Overview视图界面包括两个部分:一个是对heap dump文件的一个大致的分析,包括占用内存大小,类个数,对象个数,类加载器个数,及用饼图的方式展示对象retained size信息、另一个是提供了一些常用的入口,包括视图入口(Actions)、常用的分析报告入口(Reports)、MAT使用教程入口(Step By Step)]。

关于Overview视图区域,我们得关注点应该放在饼图上(根据retained size 对所有对象做排序,使用拼图演示结果)。我们可以方便的看到哪些对象的ratained size比较大(如果某个对象的retained size特别大。我们就要特别小心了,可能有问题了)。当我们鼠标点击每个饼图区域(对象)的时候,会弹出一个菜单,我们还可以查看相应对象的详细信息。这个菜单包含的额内容有:
image-1650267984246

  1. List objects:

    • with ontgoing references:查看当前对象持有的外部对象引用。
    • with incoming references:查看当前对象被那些外部对象所引用。
  2. Show objects by class

    • with ontgoing references: 查看这个对象类型持有的外部对象引用
    • with ontgoing references: 查看这个对象类型被哪些外部对象引用
  3. Path To GC Roots: 从对象到GC Roots的路径。这个路径解释了为什么当前对象还能存活,对分析内存泄露很有帮助。(这个查询只能针对单个对象使用)

    • with all references: 从GC Roots节点到该对象的引用路径,包含所有引用类型。
    • exclude weak references:从该对象到GC Roots节点的最短引用路径,去除弱引用。
    • exclude soft references:从该对象到GC Roots节点的最短引用路径,去除软引用。
    • exclude pahantorn references:从该对象到GC Roots节点的最短引用路径,去除虚引用。
    • exclude weak/soft references:从该对象到GC Roots节点的最短引用路径,去除弱引用,软引用。
    • exclude phantom/soft references:从该对象到GC Roots节点的最短引用路径,去除虚引用,软引用。
    • exclude phantom/weak references:从该对象到GC Roots节点的最短引用路径,去除虚引用,弱引用。
    • exclude all phantom/weak/soft etc. references:从该对象到GC Roots节点的最短引用路径,去除虚引用,弱引用,软引用。
    • exclude custom references:从该对象到GC Roots节点的最短引用路径,去除自定义引用。
  4. Merge Shortest Paths to GC Roots: 从GC Roots到对象的共同路径。

  5. Java Basics

    • References: 显示引用和对象的统计信息,列出类加载器,包括定义的类
    • Class Loader Explorer: 列出选定对象的类装载器,包括其定义的类 。
    • Customized Retained Set: 计算选中对象的保留堆,排除指定的引用。
    • Group By Value: 按对象的字符串表示形式对其进行分组。
    • Open In Domainator Tree: 对选中对象生成支配树。
    • Show As Histogram: 展示任意对象的直方图。
  6. leak Identification:内存泄露识别。

  7. Export Snapshot: 导出快照信息。

  8. Immediate Dominators: 查看某个对象的dominator

  9. Show Retained Set: 计算一个对象的保留堆大小

  10. Copy: 拷贝一些属性。

  11. Search Queries: 搜索查询相关。

Histogram视图

  • 工具栏中点击Histogram按钮(区域三常用工具栏的第二个按钮)。
  • Overview页面的Actions部分有进入Histogram视图的快捷方式。
    image-1650268268853

Histogram视图从Class类的维度展示每个Class类的实例存在的个数、 占用的Shallow内存和Retained内存大小。

咱们从Histogram视图可以看出,哪个Class类的对象实例数量比较多,以及占用的内存比较大,不过,多数情况下,在Histogram视图看到实例对象数量比较多的类都是一些基础类型,如char[](因为其构成了String)、String、byte[],所以仅从这些是无法判断出具体导致内存泄露的类或者方法的,可以使用 List objects 或 Merge Shortest Paths to GC roots 等功能继续挖掘数据。如果Histogram视图展示的数量多的实例对象不是基础类型,是有嫌疑的某个类,如项目代码中的Bean类型,那么就要重点关注了。

如果存在内存溢出,时间久了溢出类的实例数量或者内存占比会越来越多,排名也越来越靠前的。可以点击工具类上的对比图标进行对比,通过多次对比不同时间点下的直方图对比就很容易把溢出的类找出来。

每个类的详细信息(鼠标右键某个类的时候弹出框)

  1. List objects:

    • with ontgoing references:查看当前类的所有对象,并且列出这些对象持有的外部对象引用。
    • with incoming references:查看当前类的所有对象,并且列出这些对象被那些外部对象所引用。
  2. Show objects by class

    • with ontgoing references: 查看这个对象类型持有的外部对象引用
    • with ontgoing references: 查看这个对象类型被哪些外部对象引用
  3. Merge Shortest Paths to GC Roots: 从GC Roots到对象的共同路径。

  4. Java Basics:

    • References: 显示引用和对象的统计信息,列出类加载器,包括定义的类
    • Class Loader Explorer: 列出选定对象的类装载器,包括其定义的类 。
    • Customized Retained Set: 计算选中对象的保留堆,排除指定的引用。
    • Group By Value: 按对象的字符串表示形式对其进行分组。
    • Open In Domainator Tree: 对选中对象生成支配树。
    • Show As Histogram: 展示任意对象的直方图。
  5. Java Collections

    • Array Fill Ratio: 输出数组中,非基本类型、非null对象个数占数组总长度的比例。
    • Arrays Grouped By Size: 显示数组的直方图,按大小分组。
  6. leak Identification:内存泄露识别。

  7. Export Snapshot: 导出快照信息。

  8. Immediate Dominators: 查看某个对象的dominator

  9. Show Retained Set: 计算一个对象的保留堆大小

  10. Copy: 拷贝一些属性。

  11. Search Queries: 搜索查询相关。

  12. Calculate Minimum Retained Size(quick approx.): 计算最小的Ratined Size。

  13. Calculate Precise Ratined Size: 精确计算Ratined Size。

Dominator Tree视图

  • 工具栏中点击Dominator Tree按钮(区域三常用工具栏的第三个按钮)。
  • Overview页面的Actions部分有进入Dominator Tree视图的快捷方式。
    image-1650268496512

Dominator Tree视图中列出了每个对象(Object Instance)与其引用关系的树状结构,同时包含了占用内存的大小和百分比。通过Dominator Tree视图可以很容易的找出占用内存最多的几个对象(根据Retained Heap或Percentage排序)。Histogram视图和Dominator Tree视图都是可以用来帮助我们定位溢出源的。前者是基于类的角度,后者是基于对象实例的角度。Dominator Tree视图可以更方便的看出其引用关系。

Top Consumers视图

  • Overview页面的Actions部分有进入Top Consumers视图的快捷方式

Top Consumers视图以图形化的方式列出最大的object,可以按照object、class、classloader和package进行group by。说白了就是方便我们通过不同的方式找到占内存最大的对象。

  • 按照对象查看内存占用
    image-1650269162084

  • 按照类查看内存占用
    image-1650269178615

  • 按照类加载查看内存占用
    image-1650269195669

  • 按照包名查看内存占用(根据包我们知道哪些公共用的到jar或自己的包占用。这样就可以看到包和包中哪些类的占用比较高)
    image-1650269215519

Duplicate Classes视图

  • Overview页面的Actions部分有进入Duplicate Classes视图的快捷方式

Duplicate Classes视图列出了被加载多次的类,结果按类加载器进行分组,目标是加载同一个类多次被类加载器加载。使用Duplicate Classes视图很容易找到部署应用的时候使用了同一个库的多个版本的问题。

2.3MAT报告

Leak Suspects(内存泄露报告)

  • 工具栏中点击Run Expect System Test > Leak Suspects按钮(区域三常用工具栏的第六个按钮)
  • Overview页面的Reports部分有进入Leak Suspects的快捷方式

image-1650269300808

Leak Suspects 列出了MAT帮我们分析的可能有内存泄露嫌疑的地方。MAT工具分析了heap dump文件之后非常直观的展示了一个饼图,饼图深色区域被怀疑有内存泄漏的地方。而且下面会给出对怀疑内存泄露地方的具体描述信息。

针对MAT帮我们分析出来的可能有内存泄露的地方。我们也可以进入查看怀疑地方的详细信息(Leak Suspects如果有怀疑的地方)。
image-1650269351973

  • Description: 对怀疑内存泄露地方的一个描述信息

  • Shortest Paths To the Accumulation Point: 展示怀疑内存泄露对象的Path to GC Roots(就是持有可能泄漏内存对象的最近一层)。这个视图的作用是可以分析是由于和哪个GC root相连导致当前Retained Heap占用相当大的对象无法被回收。由于是分析内存泄露的报告,找到导致当前对象无法被回收的GC roots,分析这些GC roots是否合理,是有必要的。
    image-1650269387573

  • Accumulated Objects in Dominator Tree:以对象的维度展示了以怀疑对象为根的Dominator Tree支配树。 可以方便的看出受当前对象“支配”的对象中哪个占用Retained Heap比较大。
    image-1650269419991

  • Accumulated Objects by Class in Dominator Tree:展示了以对象对象为根的Dominator Tree支配树,并以Class类分组。
    image-1650269480857

  • All Accumulated Objects by Class:列举了怀疑对象所存储的所有内容。

Top Components

  • 工具栏中点击Run Expect System Test > Top Components按钮(区域三常用工具栏的第六个按钮)。
  • Overview页面的Reports部分有进入Top Components的快捷方式

可以针对那些占用堆内存超过整个堆内存1%大小的组件做一系列的分析。
image-1650269538169

点击每个组件。有可以查看每个组件的详细信息。如下图所示(点击组件之后进入)
image-1650269587709

  • 饼图:展示内存占用大小。

  • Top Consumers:以图形化的方式列出最大的object,上文中有讲到Top Consumers哦

  • Retaines Set:是这个对象本身和他持有引用的对象和这些对象的retained set所占内存大小的总和。

  • Possible Memory Waste:可能的内存垃圾。有一下几个部分。

    • Duplicate Strings: 重复的字符串。
    • Empty Collections: 空集合。
    • Collection Fill Ratios: 集合使用率。
  • Miscellaneous:

    • Soft Reference Statistics: 软引用统计。
    • Weak Reference Statistics: 弱引用统计。
    • Finalizer Statistics: Finalizer统计(设计Java Finalizer的使用)。
    • Map Collision Ratios: Map碰撞比例。

Ps: Leak Suspects用于查找内存泄漏问题,Top Components负责分析占用堆内存超过整个堆内存1%大小的组件。

2.4线程视图

线程视图首先给出了在生成快照那个时刻,JVM中的Java线程对象列表。

  • 工具栏中点击线程视图按钮
    image-1650283062586

在线程视图这个表中,可以看到以下几个信息:线程对象的名字、线程名、线程对象占用的堆内存大小、线程对象的保留堆内存大小、线程的上下文加载器、是否为守护线程。
image-1650269745519

选中某个线程对象展开,可以看到线程的调用栈和每个栈的局部变量,通过查看线程的调用栈和局部变量的内存大小,可以找到在哪个调用栈里分配了大量的内存。
image-1650269758744

3.实战

新建一个springboot项目,然后写一个while循环,用于触发OOM
image-1650282006634

编译打包成jar,然后启动,设置堆大小为20M,溢出时自动dump到当前目录下。因为这里使用的是接口请求,如果不自动dump,报错之后抛出异常线程结束方法退出内存会被回收,无法dump。jvm参数需要在-jar之前,否则不生效。

java -Xms20m -Xmx20M -XX:HeapDumpPath=./ -XX:+HeapDumpOnOutOfMemoryError -jar imageService-1.0.0.jar

使用MAT载入堆转储文件,然后点开Histogram,发现是项目里的对象占用很多,由此确定是这个对象导致的。
image-1650283171291

点开线程视图,点开第一个线程,发现是OOM的线程,然后查看溢出位置
image-1650283248044

如果这个Bean是非单例的,可能需要确定是哪个Bean导致的,可以使用查询
image-1650283697839

然后再进行排查
image-1650283886458

然后打开代码,一通debug,问题解决

参考:

Java堆分析器 - Eclipse Memory Analyzer Tool(MAT)