hao同学的技术博客

  • 首页
  • Java
    • Java
    • JVM教程
    • Java面试
    • Java并发入门
    • Java并发进阶
  • 项目
    • 从零打造项目
  • Python
    • Python
    • Python爬虫
    • 算法
  • Java框架
    • Spring
    • SpringBoot
  • 前端
    • Angular
  • 其他
    • Linux
    • SQL
  • 随笔
分享技术,记录人生
一个痴迷于技术的厨艺爱好者
  1. 首页
  2. JVM教程
  3. 正文

MAT工具使用详解

2022年5月9日 382点热度 0人点赞 0条评论

MAT工具使用详解插图

Eclipse Memory Analyzer (MAT)是一个快速且功能丰富的Java堆分析器,可帮助您发现内存泄漏并减少内存消耗。

安装并启动

直接参考 Mac下MAT的安装

需要注意的是注意 JDK 版本和 MAT 版本的映射,最新的 MAT 版本为 1.12.0,需要在 JDK11 以上运行。

如果 JDK 版本为 11,且 MAT 版本是最新的,还需要修改 /Applications/mat.app/Contents/Eclipse/MemoryAnalyzer.ini,在文件中指定 JDK 版本,增加配置如下:

-vm D:/JAVA_HOME/bin/javaw.exe

亲自尝试下载 1.8.1 和 1.9.2 版本的 MAT,但是安装并配置好,虽然可以启动,却不能正常使用(点击界面按钮没有反应),最终下载了 1.12.0 版本的 MAT。

另外考虑到自己平时接触到的项目大多都是基于 JDK8,所以我又尝试下载了 1.7.0 版本的 MAT。

注意要修改 /Applications/mat.app/Contents/Info.plist 文件内容:

<!-- to use a specific Java version (instead of the platform's default) uncomment one of the following options,
          or add a VM found via $/usr/libexec/java_home -V
        <string>-vm</string><string>/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Commands/java</string>
        <string>-vm</string><string>/Library/Java/JavaVirtualMachines/1.8.0.jdk/Contents/Home/bin/java</string>
        <string>-vm</string><string>/Library/Java/JavaVirtualMachines/jdk1.8.0_301.jdk/Contents/Home/bin/java</string>
        <string>-vm</string><string>/Library/Java/JavaVirtualMachines/jdk-9.0.4.jdk/Contents/Home/bin/java</string>
        <string>-vm</string><string>/Library/Java/JavaVirtualMachines/jdk-11.0.13.jdk/Contents/Home/bin/java</string>
      -->
      <string>-vm</string><string>/Library/Java/JavaVirtualMachines/jdk1.8.0_301.jdk/Contents/Home/bin/java</string>
      <string>-data</string><string>/Applications/mat.app/Contents/MacOS/workspace</string>

直接启动 mat.app,会提示如下错误:

无法打开“mat.app”,因为无法验证开发者。

可以修改 Mac 系统偏好设置——》安全与隐私——》点击左下角锁,输入密码——》通用——》点击允许打开。

1.7.0 版本的 MAT 启动后,UI 界面没反应,参考:https://www.eclipse.org/forums/index.php/t/1090889/,换个包即可。

mv /Users/xx/chrome下载/java相关/swt-4.7.1a-cocoa-macosx-x86_64/swt.jar /Applications/mat.app/Contents/Eclipse/plugins/org.eclipse.swt.cocoa.macosx.x86_64_3.104.2.v20160212-1350.jar

获取dump

本地准备一段代码,这个代码会导致 JVM 堆内存溢出,方便我们演示 MAT 的效果。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Goods {

  private String name;
  private Double price;
  private String[] types;
}

//-Xms60m -Xmx60m  -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/Applications/mat.app/Contents/MacOS/workspace
public class OutOfMemoryTest {

  public static void main(String[] args) throws InterruptedException {
    Map<String, Goods> map = new HashMap<>();
    int counter = 1;
    while (true) {
      Thread.sleep(10);
      Goods goods = new Goods();
      String[] types = new String[counter];
      for (int i = 0; i < types.length; i++) {
        types[i] = "type" + i;
      }
      goods.setName("hresh" + counter);
      goods.setPrice(Double.valueOf(counter));
      goods.setTypes(types);
      map.put(goods.getName(), goods);
      if (counter % 100 == 0) {
        System.out.println("put" + counter);
      }
      counter++;
    }

  }
}

上述代码是在一个无限循环体中不断创建 Goods 对象,每次循环 new 出来的对象的 types 属性还在不断的扩大。

执行上述代码时,指定好堆内存大小,过段时间就会报错,并生成 dump 文件。

启动代码,运行一段时间后报错如下:

java.lang.OutOfMemoryError: GC overhead limit exceeded
Dumping heap to /Applications/mat.app/Contents/MacOS/workspace/java_pid17847.hprof ...
Heap dump file created [74544120 bytes in 0.279 secs]
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
  at com.msdn.java.commandLine.mat.OutOfMemoryTest.main(OutOfMemoryTest.java:22)

相较于通过命令来导出 dump 文件,这种方式属于是事后处理,需要等待当前 JVM 出现问题后才能生成 dump 文件,实时性不高,而且一般也不会添加 HeapDumpOnOutOfMemoryError 参数。

关于 HeapDumpOnOutOfMemoryError 参数讲解:

-XX:+HeapDumpOnOutOfMemoryError 参数表示当JVM发生OOM时,自动生成DUMP文件。
-XX:HeapDumpPath={目录}参数表示生成DUMP文件的路径,也可以指定文件名称,例如:-XX:HeapDumpPath={目录}/java_heapdump.hprof。如果不指定文件名,默认会在项目根目录下生成一个文件,文件名格式为:java_<pid>_<date>_<time>_heapDump.hprof。

除此之外,还有几个命令:

-XX:+HeapDumpBeforeFullGC   当 JVM 执行 FullGC前
-XX:+HeapDumpAfterFullGC    当 JVM 执行 FullGC后

除了通过配置 JVM 参数来生成 dump 文件,还可以通过 jmap 工具来生成任意进程的 dump 文件。

# 先找到PID
ps -ef | grep java

# jmap 转存快照
jmap -dump:format=b,file=/opt/dump/test.dump {PID}

内存分析

使用 MAT 工具打开刚才生成的 dump 文件。需要注意的是,实际生产中获得的 dump 文件可能很大,MAT 载入会失败。可以尝试修改

/Applications/mat.app/Contents/Eclipse/MemoryAnalyzer.ini,如下所示:

-startup
../Eclipse/plugins/org.eclipse.equinox.launcher_1.3.100.v20150511-1540.jar
--launcher.library
../Eclipse/plugins/org.eclipse.equinox.launcher.cocoa.macosx.x86_64_1.1.300.v20150602-1417
-vmargs
-Xmx1024m
-Dorg.eclipse.swt.internal.carbon.smallFonts
-XstartOnFirstThread

增加一下-Xmx 的值,实在不行,我们就换用 VisualVM 来分析 dump 文件。

当加载完堆快照之后,MAT 的主界面将展示一张饼状图。

MAT主界面

MAT 计算对象占据内存的两种方式。第一种是 Shallow heap,指的是对象自身所占据的内存。第二种是 Retained heap,指的是当对象不再被引用时,垃圾回收器所能回收的总内存,包括对象自身所占据的内存,以及仅能够通过该对象引用到的其他对象所占据的内存。上面的饼状图便是基于 Retained heap 的。

可以看出,有个应用占用了大部分的堆内存,那么该应用很可能存在问题,我们继续往下看。

点击上图下方 Reports 中的 Leak Suspects,得到如下显示。

Leak Suspects

从描述上看到,主线程有个本地变量占用了很大内存,这个变量是 HashMap 的实例。

MAT 包括了两个比较重要的视图,分别是直方图(histogram)和支配树(dominator tree)。

直方图(histogram)

直方图

相较于 jmap 的-histo 命令,MAT 工具更加强大,除了能够展示各个类的实例数目以及这些实例的 Shallow heap 总和,还可以计算 Retained heap,并支持基于实例数目或 Retained heap 的排序方式(默认为 Shallow heap)。此外,MAT 还可以将直方图中的类按照超类、类加载器或者包名分组。

根据上图可知,char[] 占用内存最大,其次是 String,而我们知道 String 里用 char[] 存储数据,所以归根结底还是 String 对象占用内存过大。我们之间来看 String 是被谁引用的,选中那一行,右键点击 List objects。

  • with incoming references 表示的是当前查看的对象,被外部应用
  • with outGoing references 表示的是当前对象,引用了外部对象

histogram展示

histogram展示

结果发现是 Goods 对象的 types 属性消耗了大量内存。

支配树(dominator tree)

支配树的概念源自图论。在一则流图(flow diagram)中,如果从入口节点到 b 节点的所有路径都要经过 a 节点,那么 a 支配(dominate)b。在 a 支配 b,且 a 不同于 b 的情况下(即 a 严格支配 b),如果从 a 节点到 b 节点的所有路径中不存在支配 b 的其他节点,那么 a 直接支配(immediate dominate)b。这里的支配树指的便是由节点的直接支配节点所组成的树状结构。

MAT 将按照每个对象 Retained heap 的大小排列该支配树。如下图所示:

支配树信息展示

当在支配树视图中选中某一对象时,我们还可以通过 Path To GC Roots 功能,反向列出该对象到 GC Roots 的引用路径。如下图所示:

支配树信息展示

支配树信息展示

总结

通过 jstat 命令可以监控 GC 情况,通过查看 GC 过程中的值变化,来预测危险的发生,及时进行 GC 优化。

如果真的遇到内存溢出,我们可以通过 MAT 工具来分析错误原因,定位到具体的代码,然后处理相关问题。

推荐阅读

使用MAT命令行工具生成堆dump分析文件

本作品采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可
标签: Java JVM 内存分析
最后更新:2022年5月9日

hresh

这是一个专注于IT技术学习交流的个人技术博客网站,包括Java学习、Python爬虫、Web开发实践等领域,深耕Java领域,内容涵盖Java基础、Java并发编程、Java虚拟机、Java面试等核心知识点。

点赞
< 上一篇
下一篇 >

文章评论

取消回复

hresh

这是一个专注于IT技术学习交流的个人技术博客网站,包括Java学习、Python爬虫、Web开发实践等领域,深耕Java领域,内容涵盖Java基础、Java并发编程、Java虚拟机、Java面试等核心知识点。

文章目录
  • 安装并启动
  • 获取dump
  • 内存分析
  • 总结
  • 推荐阅读
最新 热点 随机
最新 热点 随机
后端必知:遵循Google Java规范并引入checkstyle检查 Spring Security结合Redis实现缓存功能 Spring Security结合JWT实现认证与授权 Spring Security自定义认证逻辑实现图片验证码登录 Spring Security进阶学习 Spring Security入门学习
SpringBoot集成MybatisPlus项目实操 Spring Security结合Redis实现缓存功能 使用Kettle动态生成页码并实现分页数据同步 Python3 和 Python2 区别总结 Python 判断两个单链表的交点 python3.7 安装使用 tesserocr

COPYRIGHT © 2022 hao同学的技术博客. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

鄂ICP备2022007381号

鄂公网安备 42010302002449号