java应用监测(6)-第三方内存分析工具MAT

tags: java,troubleshooting,monitor,matphp


一句话归纳:MAT是一个强大的内存分析工具,能够快捷、有效地帮助咱们找到内存泄露,减小内存消耗分析工具,下文将进行讲解。html

1 引言

以前的文章有提过,内存中堆的使用状况是应用性能监测的重点,而对于堆的快照,能够dump出来进一步分析,总的来讲,通常咱们对于堆dump快照有三种方式:java

  • 添加启动参数发生OOM时自动dump: java应用的启动参数通常最好都加上-XX:+HeapDumpOnOutOfMemoryError-XX:HeapDumpPath=logs/heapdump.hprof,即在发生OOM时自动dump堆快照,但这种方式至关来讲是滞后的(须要等到发生OOM后)。
  • 使用命令按需手动dump: 咱们也可使用jmap -dump:format=b,file=HeapDump.hprof <pid>工具手动进行堆dump和线程dump
  • 使用工具手动dump: 如上一篇文章jconsolejvisualvm的讲解中讲到,jvisualvm有提供dump堆快照的功能,点击一下便可。

那么,对于dump出来的文件,如何对它们进行分析?jvisualvm能够直接装入快照文件进行分析,而本文所介绍的MAT,相对来讲内存分析功能更强大,它能够自动检测有可能发生问题(特别是内存溢出、内存泄露)的地方,也能够详细查看类内存占用状况,方法级的调用状况等,是一个分析内存情况的不可多得的工具。git

2 MAT工具介绍

MAT(Memory Analyzer tool)是一款内存分析器工具,它是一个快速且功能丰富的堆内存分析器,帮助咱们查找内存泄漏和分析高内存消耗问题。官方网站是:https://www.eclipse.org/mat/,有兴趣能够上去看一下。使用MAT,能够轻松实现如下功能:程序员

  • 找到最大的对象,由于MAT提供显示合理的累积大小(retained size
  • 探索对象图,包括inboundoutbound引用,即引用此对象的和此对象引出的。
  • 查找没法回收的对象,能够计算从垃圾收集器根到相关对象的路径
  • 找到内存浪费,好比冗余的String对象,空集合对象等等

3 MAT工具安装

MAT安装有两种方式,一种是以eclipse插件方式安装,一种是独立安装。在MAT官方文档中有相应的安装文件下载,下载地址为:https://www.eclipse.org/mat/downloads.phpgithub

  • 若使用eclipse插件安装,help -> install new soft点击ADD,在弹出框中添加插件地址:http://download.eclipse.org/mat/1.9.0/update-site/,也能够直接在下载页面下载离线插件包,以离线方式安装。
  • 独立安装,windows下,根据系统直接下载Windows (x86)Windows (x86_64),下载时能够选择适合本身的镜像,双击安装便可。

4 MAT工具使用

MAT定位是内存分析工具,它的主要功能就是对内存快照进行分析,帮助咱们找到有可能内存溢出或内存泄漏的地方,所以,找到占用内存大的对象和找出没法回收的对象是其主要目的。MAT官方文档,地址以下:https://help.eclipse.org/2019-06/index.jsp?topic=/org.eclipse.mat.ui.help/welcome.html,对MAT的使用进行描述,有兴趣的同窗能够上去看看。下面主要对MAT的经常使用概念、经常使用的功能进行介绍。spring

下文中,以java-monitor-example(https://github.com/mianshenglee/my-example/tree/master/java-monitor-example)为例,此示例是一个简单的spring boot工程,里面的一个controller中的user/oom接口调用的service对象经过List成员不断地添加User对象,最终致使OOM的发生,应用的启动参数是-Xms64m -Xmx64m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${APP_HOME}/logs/heapdump.hprofbootstrap

4.1 MAT相关概念说明

4.1.1 内存泄漏与内存溢出

  • 内存泄露:对象已经没用了(不被任何程序逻辑所须要),还存在被根元素引用的状况,没法经过垃圾收集器进行自动回收,须要经过找出泄漏的代码位置和缘由,才好肯定解决方案;
  • 内存溢出:内存中的对象都还存活着,JVM的堆分配空间不足,须要检查堆设置大小(-Xmx与-Xms),代码是否存在对象生命周期太长、持有状态时间过长的状况。

4.1.2 引用(强引用,软引用,弱引用,虚引用)

  • Strong Ref(强引用):强可达性的引用,对象保存在内存中,只有去掉强可达,对象才被回收,一般咱们编写的代码都是Strong Ref。windows

  • Soft Ref(软引用):对应软可达性,只要有足够的内存,就一直保持对象,直到发现内存吃紧且没有Strong Ref时才回收对象。通常可用来实现缓存,经过java.lang.ref.SoftReference类实现。数组

  • Weak Ref(弱引用):比Soft Ref更弱,当发现不存在Strong Ref时,马上回收对象而没必要等到内存吃紧的时候。经过java.lang.ref.WeakReference和java.util.WeakHashMap类实现。

  • Phantom Ref(虚引用):根本不会在内存中保持任何对象,你只能使用Phantom Ref自己。通常用于在进入finalize()方法后进行特殊的清理过程,经过 java.lang.ref.PhantomReference实现。

4.1.3 shallow heapretained heap

  • shallow heap:对象自己占用内存的大小,也就是对象头加成员变量(不是成员变量的值)的总和,如一个引用占用32或64bit,一个integer占4bytes,Long占8bytes等。如简单的一个类里面只有一个成员变量int i,那么这个类的shallo size是12字节,由于对象头是8字节,成员变量int是4字节。常规对象(非数组)的Shallow size有其成员变量的数量和类型决定,数组的shallow size有数组元素的类型(对象类型、基本类型)和数组长度决定。
  • retained heap:若是一个对象被释放掉,那会由于该对象的释放而减小引用进而被释放的全部的对象(包括被递归释放的)所占用的heap大小,即对象X被垃圾回收器回收后能被GC从内存中移除的全部对象之和。相对于shallow heap,Retained heap能够更精确的反映一个对象实际占用的大小(若该对象释放,retained heap均可以被释放)。

4.1.4 outgoing referencesincoming references

  • outgoing references :表示该对象的出节点(被该对象引用的对象)。
  • incoming references :表示该对象的入节点(引用到该对象的对象)。

4.1.5 Dominator Tree

将对象树转换成Dominator Tree能帮助咱们快速的发现占用内存最大的块,也能帮咱们分析对象之间的依赖关系。Dominator Tree有如下几个定义:

  • 对象X Dominator(支配)对象Y,当且仅当在对象树中全部到达Y的路径都必须通过X
  • 对象Y的直接Dominator,是指在对象树中距离Y最近的Dominator
  • Dominator tree利用对象树构建出来。在Dominator tree中每个对象都是他的直接Dominator的子节点。

对象树和Dominator tree的对应关系以下:

如上图,由于A和B都引用到C,因此A释放时,C内存不会被释放。因此这块内存不会被计算到A或者B的Retained Heap中,所以,对象树在转换成Dominator tree时,会A、B、C三个是平级的。

4.1.6 Garbage Collection Roots(GC root)

在执行GC时,是经过对象可达性来判断是否回收对象的,一个对象是否可达,也就是看这个对象的引用连是否和GC Root相连。一个GC root指的是能够从堆外部访问的对象,有如下缘由可使一个对象成为GC root对象。

  • System Class: 经过bootstrap/system类加载器加载的类,如rt.jar中的java.util.*
  • JNI Local: JNI方法中的变量或者方法形参
  • JNI Global:JNI方法中的全局变量
  • Thread Block:线程里面的变量,一个活着的线程里面的对象确定不能被回收
  • Thread:处于激活状态的线程
  • Busy Monitor:调用了wait()、notify()方法,或者是同步对象,例如调用synchronized(Object) 或者进入一个synchronized方法后的当前对象
  • Java Local:本地变量,例如方法的输入参数或者是方法内部建立的仍在线程堆栈里面的对象
  • Native Stack:Java 方法中的变量或者方法形参.
  • Finalizable:等待运行finalizer的对象
  • Unfinalized:有finalize方法,但未进行finalized,且不在finalizer队列的对象。
  • Unreachable:经过其它root都不可达的对象,MAT会把它标记为root以便于分析回收。
  • Java Stack Frame:java栈帧
  • Unknown

4.2 Leak Suspects自动分析泄漏

当发生OOM获取到heapdump.hprof文件或者手动dump出文件后,使用MAT打开文件。打开后默认会提示是否进行内存泄漏检测报告(若是打开Dump时跳过了的话,也能够从其它入口进入工具栏上的 Run Expect System Test -> Leak Suspects),以下图所示:

选择是后进入报告内容,此报告内容会帮咱们分析的可能有内存泄露嫌疑的地方,它会把累积内存占用较大的经过饼状图显示出来。以下图所示:

如上图,报告已指出UserService占用了76.73%的内存,而这些内存都在Object[]这个数组中。所以,很大一种多是这个对象中的数组数量过大。点击Details能够查看更详细的内容:

在此详细而,Shortest Paths To the Accumulation Point能够显示出到GC roots的最短路径,由此能够分析是因为和哪一个GC root相连致使当前Retained Heap占用至关大的对象没法被回收,本示例中,GC root是线程的本地变量(java local)。Accumulated Objects in Dominator TreeDominator Tree为视图,能够方便的看出受当前对象“支配”的对象中哪一个占用Retained Heap比较大。图中可看出对象都在ArrayList中,而ArrayList下有Object数组,数组下是User对象。此能够知道问题出在哪里了。须要针对这个位置,查看代码,找出致使这个数组存储的User过量的缘由。

注:在原始堆转储文件的目录下,MAT已经将报告的内容压缩打包到一个zip文件,命名为“xxx_Leak_Suspects.zip”,整个报告是一个HTML格式的文件,能够用浏览器直接打开查看,能够方便进行报告分发、分享。

4.3 histogram视图查看对象个数与大小

点击Overview页面Actions区域内的“Histogram视图”或点击工具栏的“histogram”按钮,能够显示直方图列表,它以Class类的维度展现每一个Class类的实例存在的个数、 占用的Shallow heapRetained内存大小,能够分别排序显示。以下图所示:

Shallow HeapRetained Heap的概念前面已经讲过。 对于java的对象,其成员基本都是引用。真正的内存都在堆上,看起来是一堆原生的byte[], char[], int[],所以若是只看对象自己的内存,数量都很小,而多数状况下,在Histogram视图看到实例对象数量比较多的类都是一些基础类型(一般都排在前面),如char[]、String、byte[],因此仅从这些是没法判断出具体致使内存泄露的类或者方法的。从上图中,能够直接看到User对象的数量不少,有时不容易看出来的,可使用工具栏中的group result by 能够以super class,class loader等排序,须要特别注意自定义的classLoader,以下图:

一般,若是Histogram视图展现的数量多的实例对象不是基础类型,而是用户自定义的类或者有嫌疑的类,就要重点关注查看。想进一步分析的,能够右键,选择使用List objectsMerge Shortest Paths to GC roots等功能继续钻取数据。其中list objects分别有outgoing referencesincoming references,能够找出由这个对象出去的引用及经过哪些引用到这个对象的。Merge Shortest Paths to GC roots能够排除掉全部不是强引用的,找到这个对象的到GC root的引用路径。最终目的就是找到占用内存最大对象和没法回收的对象,计算从垃圾收集器根到相关对象的路径,从而根据这个对象路径去检查代码,找出问题所在。

4.4 Dominator Tree视图

点击Overview页面Actions区域内的“Dominator Tree视图”或点击工具栏的“open Dominator Tree”按钮,能够进入 Dominator Tree视图。该视图以实例对象的维度展现当前堆内存中Retained Heap占用最大的对象,以及依赖这些对象存活的对象的树状结构。以下图:

视图展现了实例对象名、Shallow Heap大小、Retained Heap大小、以及当前对象的Retained Heap在整个堆中的占比。该图是树状结构的,当上一级的对象被回收,那么,它引用的子对象都会被回收,这也是Dominator的意义,当父节点的回收会致使子节点也被回收。经过此视图,能够很方便的找出占用Retained Heap内存最多的几个对象,并表示出某些objects的是由于哪些objects的缘由而存活。本示例中,能够看出是因为UserService中的ArrayList引用的数组存储过量的User对象。

4.5 线程视图查看线程栈运行状况

点击工具栏的“齿轮”按钮,能够打开Thread Overview视图,能够查看线程的栈帧信息,包括线程对象/线程栈信息、线程名、Shallow HeapRetained Heap、类加载器、是否Daemon线程等信息。结合内存Dump分析,看到线程栈帧中的本地变量,在左下方的对象属性区域还能看到本地变量的属性值。如按上面的示例(shortest paths to GC root),知道是因为线程Thread-12GC-root占用内存大,在线程视图中,就能够着重看它的属性状况,以下图:

由上图能够看到此线程调用WithOOM方法,使用了变量UserService,而变量使用了userList,它包含了大量的User对象,占用retained heap很大。

5 总结

对于java应用的内存分析,须要对java应用的内存进行dump操做,生成内存快照后,使用MAT进行分析,找出大对象,找到内存泄漏或溢出的地方,从而分析代码,解决问题。本文对MAT的使用场景,基本概念,安装、使用进行了详细介绍,你们能够本身安装一下,写个小示例或者拿本文中的示例,实践一下。经过本文,但愿能够帮助你们更方便,更有效率地分析内存,解决java应用的内存故障问题。

参数资料

  • MAT官网:https://www.eclipse.org/mat/
  • MAT下载:http://www.eclipse.org/mat/downloads.php
  • MAT使用手册:https://help.eclipse.org/2019-06/index.jsp?topic=/org.eclipse.mat.ui.help/welcome.html
  • Shallow and retained sizes:https://www.yourkit.com/docs/java/help/sizes.jsp
  • 使用Eclipse Memory Analyzer Tool(MAT)分析线上故障(一) - 视图&功能篇:https://www.cnblogs.com/trust-freedom/p/6744948.html
  • 示例代码地址:https://github.com/mianshenglee/my-example/tree/master/java-monitor-example

相关阅读

相关文章
相关标签/搜索