1. 介绍

Eclipse MAT是eclipse提供的插件用于分析JAVA heap。试过很多分析heap的,但是没有比这个更好用的了。

PS:在mac high sierra上直接下载独立运行的MAT貌似会出问题卡死,我是本地直接下载了个。

请先阅读下MAT官方的DOC:MAT官方DOC

2. 基本概念

2.1 堆中包含的内容

  • All Objects:Class, fields, primitive values and references
  • All Classes:Classloader, name, super class, static fields
  • Garbage Collection Roots:Objects defined to be reachable by the JVM
  • Thread Stacks and Local Variables:The call-stacks of threads at the moment of the snapshot, and per-frame information about local objects

2.2 Shallow VS Retined heap

Shallow heap size : 对象本身占用的大小,不包含其引用的对象
Retained heap: 对象本身大小加上其所有引用对象的真实大小

PS: 可见实际占用看见或者回收的空间要看Retained heap

2.3 Dominator Tree

一个Object构成的树(DAG),最顶端的节点的retained memory由其所有子节点构成,作用主要是方便找占用retained heap size最大的对象

2.4 gc root

GC ROOT本质就是一个对象,这个对象包含了一系列必须保证活跃的引用(可以理解成一个DAG)。JVM的GC就是判断哪些不能被回收,剩下的全部回收就行。即,遍历所有对象做GC ROOT可达性分析,可达的则不回收,不可达的则进行GC。

用MAT文档里面的话来解释就是:
A garbage collection root is an object that is accessible from outside the heap. The following reasons make an object a GC root

可作为gc root的对象,完整列表参考下MAT官方文档,我重点罗列几个

  1. JNI相关的对象和引用
  2. 静态变量
  3. 持有监视器锁的对象
  4. native栈上的对象和引用

2.5 一些选项说明

  • biggest object by retained size:显示在内存较大的对象信息
  • list objects -- with outgoing references : 查看这个对象持有的外部对象引用。
  • list objects -- with incoming references : 查看这个对象被哪些外部对象引用。
  • show objects by class -- with outgoing references :查看这个对象类型持有的外部对象引用
  • show objects by class -- with incoming references :查看这个对象类型被哪些外部对象引用
  • paths to gc root : 显示不同类型引用(上文中提到的Strong ,soft,weak )到跟节点的路径。
  • merge shorest path to gc root : 合并最短路径到root节点。
  • java basics:
    • classloader 该对象对应的classloader信息 。
    • thread details :线程信息
    • thread stacks :线程堆栈
    • find String : 在这个对象中查询需要的字符串
    • group by : 根据某个字段统计出现的个数
  • leak Identification -- top consumers :几个大消耗内存的对象

另外计算retsined size也是很常用的功能,MAT默认不显示这一列,如果有需要的可以自己勾选算一下:

3. 实践

3.1 classloader内存泄漏排查

参考文献2和4讲了classloader内存泄漏问题排查方法,可以学习下。我没有内存泄漏的例子,但是实际过程中有碰到个perm oom的问题。

例如产生一个perm gen OOM,就猜测可能由classloader内存泄漏引起。因为perm区主要就存放一些class元数据、静态变量。比如有时候载入了一些java agent,一些classloader载入了不少类,但是分配的perm gen太小,就导致perm gen OOM了。当然这个OOM也不一定是内存泄漏,可能是分配的perm区真的太小了,我就碰到了这样的情况:

总共分配了32M的PERM,平时使用就达到了29M,因为开了个java agent多加了几M就直接OOM了,因此也顺便用MAT 分析了下heap

打开classloader浏览器

看classloader的retained size,合起来差不都32M的样子,有个叫ArthasClasssLoader的,就是我说的java agent,差不多占用2M。。也是本次OOM的罪魁祸首。

PS: classLoader和一般的Obect不太一样,右键勾选的时候会问你要看Class Loader本身的信息还是其定义的Classses信息。class Loader本身信息如果查看Immediate Dominators信息的话只有个ROOT。这也很好理解,class Loader是rt.jar里面的,是通过boorstrap classloader加载的,属于system class,自然其gc不会依赖别的class,属于最dominant的class了

3.2 某些类一直没有被GC问题排查

有时候要看为什么这个类没有被GC,不符合预期,那么只要看看他在哪些GC ROOT对象的引用链上。按照如下操作,这里我排除了一些虚、弱软引用,只关注强引用即可。因为其他程度弱的引用反正都会被GC的,不会是对象异常没被回收的罪魁祸首。


最后按照罗列出来的引用链,下钻去查看,肯定最后会找到一个gc root object:


这里可以看到这个Java Local类型的对象就是之前罗列的可以作为gc root object的

3.3 immediate object和gc root来查找某个对象的root dominated not的区别

某个对象肯定会关联一个immediate object,当这个immediate object的所有实例被GC后,那么这个对象也肯定会被GC。通过如下方式查找关联的immediate object是最快。但是这种方式只能查找和哪个immediate object关联,不能查看immediate object的引用信息。

如果需要查看immediate dominators的引用信息,还要按照如下操作,该dominator object有哪个外部对象持有的引用,从而了解需要哪些对象

当然很容易联想到用GC ROOT查找也是可以的,只不过gc root查找的话展现的内容层次更多些,本质上一样的, 可以看到最后都是定位到ResuableIterator@)xe0a54ce0这个对象

3.4 查看某个对象dominate 了哪些对象

3.3节我们是查看某个对象被其他什么对象和引用dominate。有时候需要查看某个对象dominate 哪些子对象也很简单,直接操作Open in Dominatoe Tree

3.5 线程相关

持有某个对象实例的线程,直接按照如下操作即可:

4. 总结

MAT功能强大,本身针对其主要用法配合一些应用场景做了一些介绍,更多可以自己再配合官方文档使用一番,加强理解。

参考资料

  1. 知乎提问R大的回答,理解gc root
  2. classloader内存泄露的总结
  3. Classloader leaks I – How to find classloader leaks with Eclipse Memory Analyser (MAT)
  4. Solve "PermGen space OutOfMemoryError"