在文章eclipse MAT (二)进行OutOfMemoryError的诊断分析
生成了一个文件java_pid3708.hprof,这个文件 在你的项目的根目录下
一,生成分析报告
首先,启动前面安装配置好的 Memory Analyzer tool , 然后选择菜单项 File- Open Heap Dump 来加载需要分析的堆转储文件。文件加载完成后,你可以看到如图 1所示的界面:
图 1. 概览
通过上面的概览,我们对内存占用情况有了一个总体的了解。
从上图可以看到它的大部分功能,在饼图上,你会发现转储的大小和数量的类,对象和类加载器。
正确的下面,饼图给出了一个印象最大的对象转储。移动你的鼠标一片看到对象中的对象的细节检查在左边。下面的Action标签中:
- Histogram可以列出内存中的对象,对象的个数以及大小。
-
Dominator Tree可以列出那个线程,以及线程下面的那些对象占用的空间。
-
Top consumers通过图形列出最大的object。
-
Leak Suspects通过MA自动分析泄漏的原因。
Histogram
- Class Name : 类名称,java类名
-
Objects : 类的对象的数量,这个对象被创建了多少个
-
Shallow Heap :一个对象内存的消耗大小,不包含对其他对象的引用
-
Retained Heap :是shallow Heap的总和,也就是该对象被GC之后所能回收到内存的总和
一般来说,Shallow Heap堆中的对象是它的大小和保留内存大小相同的对象是堆内存的数量时,将释放对象被垃圾收集。
保留设置一组主要的对象,例如一个特定类的所有对象,或所有对象的一个特定的类装入器装入的类或者只是一群任意对象,是释放的组对象如果所有对象的主要设置变得难以接近的。保留设置包括这些对象以及所有其他对象只能通过这些对象。保留大小是总堆大小中包含的所有对象的保留。摘自eclipse
关于的详细讲解,建议大家查看Shallow heap & Retained heap,这是个很重要的概念。
这儿借助工具提供的regex正则搜索一下我们自己的类,排序后看看哪些相对是占用比较大的。
左边可以看到类的详细使用,比如所属包,父类是谁,所属的类加载器,内存地址,占用大小和回收情况等
这儿有个工具可以根据自己的需求分组查找,默认根据class分组,类似我们sql里的group by了~~
这里可以看到上面3个选项,分别生成overview、leak suspects、top components数据,但是这儿生成的不是图表,如果要看图表在(Overview)中的Action标签里点击查看。
这个是Overview中的 Heap Dump Overview视图,从工具栏中点开,这是一个全局的内存占用信息
Used heap dump | 79.7 MB |
Number of objects | 1,535,626 |
Number of classes | 8,459 |
Number of class loaders | 74 |
Number of GC roots | 2,722 |
Format | hprof |
JVM version | |
Time | 格林尼治标准时间+0800上午9时20分37秒 |
Date | 2014-7-2 |
Identifier size | 32-bit |
File path | E:\jmap\map.bin |
File length | 108,102,005 |
|
然后可以点开SystemProperties和Thread Overview进行查看,我这里就不贴了内容比较多。
Dominator Tree
我们可以看到ibatis占了较多内存
Top consumers
这张图展示的是占用内存比较多的对象的分布,下面是具体的一些类和占用。
按等级分布的类使用情况,其实也就是按使用次数查看,java.lang.Class被排在第一
还有一张图是我们比较关心的,那就是按包名看占用,根据包我们知道哪些公共用的到jar或自己的包占用
这样就可以看到包和包中哪些类的占用比较高。
2,先检查一下 MAT 生成的一系列文件。
可以看到 MAT 工具提供了一个很贴心的功能,将报告的内容压缩打包到一个 zip 文件,并把它存放到原始堆转储文件的存放目录下,这样如果您需要和同事一起分析这个内存问题的话,只需要把这个小小的 zip 包发给他就可以了,不需要把整个堆文件发给他。并且整个报告是一个 HTML 格式的文件,用浏览器就可以轻松打开。
接下来我们就可以来看看生成的报告都包括什么内容,能不能帮我们找到问题所在吧。您可以点击工具栏上的 Leak Suspects 菜单项来生成内存泄露分析报告,也可以直接点击饼图下方的 Reports->Leak Suspects 链接来生成报告。
二、分析三步曲
通常我们都会采用下面的“三步曲”来分析内存泄露问题:
首先,对问题发生时刻的系统内存状态获取一个整体印象。
第二步,找到最有可能导致内存泄露的元凶,通常也就是消耗内存最多的对象
接下来,进一步去查看这个内存消耗大户的具体情况,看看是否有什么异常的行为。
下面将用一个基本的例子来展示如何采用“三步曲”来查看生产的分析报告。
查看报告之一:内存消耗的整体状况
如图 4 所示,在报告上最醒目的就是一张简洁明了的饼图,从图上我们可以清晰地看到一个可疑对象消耗了系统 99% 的内存。
在图的下方还有对这个可疑对象的进一步描述。我们可以看到内存是由 java.util.Vector 的实例消耗的,com.ibm.oti.vm.BootstrapClassLoader 负责这个对象的加载。这段描述非常短,但我相信您已经可以从中找到很多线索了,比如是哪个类占用了绝大多数的内存,它属于哪个组件等等。
接下来,我们应该进一步去分析问题,为什么一个 Vector 会占据了系统 99% 的内存,谁阻止了垃圾回收机制对它的回收。
查看报告之二:分析问题的所在
首先我们简单回顾下 JAVA 的内存回收机制,内存空间中垃圾回收的工作由垃圾回收器 (Garbage Collector,GC) 完成的,它的核心思想是:对虚拟机可用内存空间,即堆空间中的对象进行识别,如果对象正在被引用,那么称其为存活对象,反之,如果对象不再被引用,则为垃圾对象,可以回收其占据的空间,用于再分配。
在垃圾回收机制中有一组元素被称为根元素集合,它们是一组被虚拟机直接引用的对象,比如,正在运行的线程对象,系统调用栈里面的对象以及被 system class loader 所加载的那些对象。堆空间中的每个对象都是由一个根元素为起点被层层调用的。因此,一个对象还被某一个存活的根元素所引用,就会被认为是存活对象,不能被回收,进行内存释放。因此,我们可以通过分析一个对象到根元素的引用路径来分析为什么该对象不能被顺利回收。如果说一个对象已经不被任何程序逻辑所需要但是还存在被根元素引用的情况,我们可以说这里存在内存泄露。
现在,让我们开始真正的寻找内存泄露之旅,点击“Details ”链接,可以看到如图 5 所示对可疑对象 1 的详细分析报告。
- 我们查看下从 GC 根元素到内存消耗聚集点的最短路径:
我们可以很清楚的看到整个引用链,内存聚集点是一个拥有大量对象的集合,如果你对代码比较熟悉的话,相信这些信息应该能给你提供一些找到内存泄露的思路了。
点击鼠标,在List Objects-> with outgoing references下可以查看该类都引用了什么对象,由此查看是否因为其他对象导致的内存问题。
下面继续查看pool的gc ROOT
如下图所示的上下文菜单中选择 Path To GC Roots -> exclude weak references, 过滤掉弱引用,因为在这里弱引用不是引起问题的关键。
进入查看即可,我这儿的代码没有问题,就不用贴了。
接下来,我们再继续看看,这个对象集合里到底存放了什么,为什么会消耗掉如此多的内存。
在这张图上,我们可以清楚的看到,这个对象集合中保存了大量 Person 对象的引用,就是它导致的内存泄露。
另外推荐学习资料:
https://blog.csdn.net/alli0968/article/details/52460008?utm_source=blogxgwz2