tags: java,troubleshooting,monitor,matphp
一句话归纳:
MAT
是一个强大的内存分析工具,能够快捷、有效地帮助咱们找到内存泄露,减小内存消耗分析工具,下文将进行讲解。html
以前的文章有提过,内存中堆的使用状况是应用性能监测的重点,而对于堆的快照,能够dump出来进一步分析,总的来讲,通常咱们对于堆dump快照有三种方式:java
-XX:+HeapDumpOnOutOfMemoryError
及-XX:HeapDumpPath=logs/heapdump.hprof
,即在发生OOM时自动dump堆快照,但这种方式至关来讲是滞后的(须要等到发生OOM后)。jmap -dump:format=b,file=HeapDump.hprof <pid>
工具手动进行堆dump和线程dumpjconsole
及jvisualvm
的讲解中讲到,jvisualvm有提供dump堆快照的功能,点击一下便可。那么,对于dump出来的文件,如何对它们进行分析?jvisualvm
能够直接装入快照文件进行分析,而本文所介绍的MAT
,相对来讲内存分析功能更强大,它能够自动检测有可能发生问题(特别是内存溢出、内存泄露)的地方,也能够详细查看类内存占用状况,方法级的调用状况等,是一个分析内存情况的不可多得的工具。git
MAT(Memory Analyzer tool)是一款内存分析器工具,它是一个快速且功能丰富的堆内存分析器,帮助咱们查找内存泄漏和分析高内存消耗问题。官方网站是:https://www.eclipse.org/mat/
,有兴趣能够上去看一下。使用MAT,能够轻松实现如下功能:程序员
retained size
)inbound
和outbound
引用,即引用此对象的和此对象引出的。MAT
安装有两种方式,一种是以eclipse
插件方式安装,一种是独立安装。在MAT
的官方文档中有相应的安装文件下载,下载地址为:https://www.eclipse.org/mat/downloads.php
github
help -> install new soft
点击ADD
,在弹出框中添加插件地址:http://download.eclipse.org/mat/1.9.0/update-site/
,也能够直接在下载页面下载离线插件包,以离线方式安装。Windows (x86)
或Windows (x86_64)
,下载时能够选择适合本身的镜像,双击安装便可。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.hprof
。bootstrap
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实现。
shallow heap
及retained 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均可以被释放)。outgoing references
与incoming references
outgoing references
:表示该对象的出节点(被该对象引用的对象)。incoming references
:表示该对象的入节点(引用到该对象的对象)。Dominator Tree
将对象树转换成Dominator Tree
能帮助咱们快速的发现占用内存最大的块,也能帮咱们分析对象之间的依赖关系。Dominator Tree
有如下几个定义:
Dominator
(支配)对象Y,当且仅当在对象树中全部到达Y的路径都必须通过XDominator
,是指在对象树中距离Y最近的Dominator
Dominator tree
利用对象树构建出来。在Dominator tree
中每个对象都是他的直接Dominator
的子节点。对象树和Dominator tree
的对应关系以下:
如上图,由于A和B都引用到C,因此A释放时,C内存不会被释放。因此这块内存不会被计算到A或者B的Retained Heap中,所以,对象树在转换成Dominator tree
时,会A、B、C三个是平级的。
在执行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
当发生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 Tree
以Dominator Tree
为视图,能够方便的看出受当前对象“支配”的对象中哪一个占用Retained Heap比较大。图中可看出对象都在ArrayList
中,而ArrayList
下有Object
数组,数组下是User
对象。此能够知道问题出在哪里了。须要针对这个位置,查看代码,找出致使这个数组存储的User
过量的缘由。
注:在原始堆转储文件的目录下,
MAT
已经将报告的内容压缩打包到一个zip文件,命名为“xxx_Leak_Suspects.zip”,整个报告是一个HTML格式的文件,能够用浏览器直接打开查看,能够方便进行报告分发、分享。
点击Overview
页面Actions
区域内的“Histogram视图”或点击工具栏的“histogram”按钮,能够显示直方图列表,它以Class类的维度展现每一个Class类的实例存在的个数、 占用的Shallow heap
和 Retained内存
大小,能够分别排序显示。以下图所示:
Shallow Heap
与Retained Heap
的概念前面已经讲过。 对于java的对象,其成员基本都是引用。真正的内存都在堆上,看起来是一堆原生的byte[], char[], int[],所以若是只看对象自己的内存,数量都很小,而多数状况下,在Histogram视图看到实例对象数量比较多的类都是一些基础类型(一般都排在前面),如char[]、String、byte[],因此仅从这些是没法判断出具体致使内存泄露的类或者方法的。从上图中,能够直接看到User
对象的数量不少,有时不容易看出来的,可使用工具栏中的group result by
能够以super class
,class loader
等排序,须要特别注意自定义的classLoader
,以下图:
一般,若是Histogram视图展现的数量多的实例对象不是基础类型,而是用户自定义的类或者有嫌疑的类,就要重点关注查看。想进一步分析的,能够右键,选择使用List objects
或 Merge Shortest Paths to GC roots
等功能继续钻取数据。其中list objects
分别有outgoing references
及incoming references
,能够找出由这个对象出去的引用及经过哪些引用到这个对象的。Merge Shortest Paths to GC roots
能够排除掉全部不是强引用的,找到这个对象的到GC root
的引用路径。最终目的就是找到占用内存最大对象和没法回收的对象,计算从垃圾收集器根到相关对象的路径,从而根据这个对象路径去检查代码,找出问题所在。
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
对象。
点击工具栏的“齿轮”按钮,能够打开Thread Overview
视图,能够查看线程的栈帧信息,包括线程对象/线程栈信息、线程名、Shallow Heap
、Retained Heap
、类加载器、是否Daemon线程等信息。结合内存Dump分析,看到线程栈帧中的本地变量,在左下方的对象属性区域还能看到本地变量的属性值。如按上面的示例(shortest paths to GC root
),知道是因为线程Thread-12
是GC-root
占用内存大,在线程视图中,就能够着重看它的属性状况,以下图:
由上图能够看到此线程调用WithOOM
方法,使用了变量UserService
,而变量使用了userList
,它包含了大量的User
对象,占用retained heap
很大。
对于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
https://www.yourkit.com/docs/java/help/sizes.jsp
https://www.cnblogs.com/trust-freedom/p/6744948.html
https://github.com/mianshenglee/my-example/tree/master/java-monitor-example