最近拜读周晓明的《深入分析JVM虚拟机》,针对书中第二章HeapOOM的例子,用MAT工具分析一下。
一.编写测试代码并生成dump文件
package test;
import java.util.ArrayList;
import java.util.List;
public class HeapOOM {
static class OOMObject {}
public HeapOOM() {
}
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<OOMObject>();
while(true){
list.add(new OOMObject());
}
}
}
运行该类并配置参数:
重点是图中红框内的参数配置,该参数会在程序发生内存泄露时在项目根路径下导出.hprof的文件,该文件即是我们稍后要用Eclipse Memory Analyzer tool(MAT)工具分析的文件。
补充:
生成堆转存文件的另一种方式是通过Jmap
用 jmap 生产 dump 文件
jmap -dump:format=b,file=HeapDump.bin <pid>
二.运行HeapOOM
结果:
[GC [PSYoungGen: 7474K->1016K(9216K)] 7474K->5209K(19456K), 0.0226528 secs] [Times: user=0.03 sys=0.02, real=0.02 secs]
[GC-- [PSYoungGen: 9208K->9208K(9216K)] 13401K->19440K(19456K), 0.0328609 secs] [Times: user=0.05 sys=0.01, real=0.03 secs]
[Full GC [PSYoungGen: 9208K->0K(9216K)] [ParOldGen: 10232K->9926K(10240K)] 19440K->9926K(19456K) [PSPermGen: 2652K->2651K(21504K)], 0.4197879 secs] [Times: user=0.36 sys=0.00, real=0.42 secs]
[Full GC [PSYoungGen: 7517K->7996K(9216K)] [ParOldGen: 9926K->7816K(10240K)] 17444K->15813K(19456K) [PSPermGen: 2651K->2651K(21504K)], 0.4042434 secs] [Times: user=0.39 sys=0.00, real=0.40 secs]
[Full GC [PSYoungGen: 7996K->7987K(9216K)] [ParOldGen: 7816K->7816K(10240K)] 15813K->15803K(19456K) [PSPermGen: 2651K->2651K(21504K)], 0.2645415 secs] [Times: user=0.30 sys=0.00, real=0.27 secs]
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid6056.hprof ...
Heap dump file created [27909372 bytes in 0.280 secs]
Heap
PSYoungGen total 9216K, used 8192K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
eden space 8192K, 100% used [0x00000000ff600000,0x00000000ffe00000,0x00000000ffe00000)
from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
to space 1024K, 47% used [0x00000000fff00000,0x00000000fff78868,0x0000000100000000)
ParOldGen total 10240K, used 7816K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
object space 10240K, 76% used [0x00000000fec00000,0x00000000ff3a21c0,0x00000000ff600000)
PSPermGen total 21504K, used 2681K [0x00000000f9a00000, 0x00000000faf00000, 0x00000000fec00000)
object space 21504K, 12% used [0x00000000f9a00000,0x00000000f9c9e5f8,0x00000000faf00000)
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Unknown Source)
at java.util.Arrays.copyOf(Unknown Source)
at java.util.ArrayList.grow(Unknown Source)
at java.util.ArrayList.ensureExplicitCapacity(Unknown Source)
at java.util.ArrayList.ensureCapacityInternal(Unknown Source)
at java.util.ArrayList.add(Unknown Source)
at test.HeapOOM.main(HeapOOM.java:19)
三.使用MAT进行分析
1.MAT安装
在分析之前简单说一下MAT工具的安装。
下载地址:http://www.eclipse.org/mat/downloads.php
选择对应的操作系统的版本进行下载。
我选择的是windows(x86_64)
下载解压之后:
2.使用MAT分析
用MAT打开 java_pid6056.hprof
说明:
(1)Histogram
Histogram 可以列出内存中每个类所对应的实例个数以及大小。
如下所示:
Objects:类的实例对象的数量。
Shallow size:就是对象本身占用内存的大小,不包含对其他对象的引用,也就是对象头加成员变量(不是成员变量的值)的总和。
Retained size:是该对象自己的 shallow size,加上从该对象能直接或间接访问到对象的 shallow size 之和。换句话说,retained size 是该对象被 GC 之后所能回收到内存的总和。
我们发现 HeapOOM$OOMObject类的对象占用了很多空间。
(2)Leak Suspects
其中,深蓝色的代表可疑对象,该对象是内存消耗大户。
在下面的描述中,可以看到内存被急速消耗的主要愿意是system class loader,也就是系统类加载器(应用加载器)加载object[]导致的,对应我们的代码中的
List<OOMObject> list = new ArrayList<OOMObject>();
while(true){
list.add(new OOMObject());
}
立刻定位到问题所在。
接下来,我们应该进一步去分析问题,为什么一个 List会占据了系统 97% 的内存,谁阻止了垃圾回收机制对它的回收呢?
首先我们简单回顾下 JAVA 的内存回收机制,内存空间中垃圾回收的工作由垃圾回收器 (Garbage Collector,GC) 完成的,它的核心思想是:对虚拟机可用内存空间,即堆空间中的对象进行识别,如果对象正在被引用,那么称其为存活对象,反之,如果对象不再被引用,则为垃圾对象,可以回收其占据的空间,用于再分配。
在垃圾回收机制中有一组元素被称为根元素集合,它们是一组被虚拟机直接引用的对象,比如,正在运行的线程对象,系统调用栈里面的对象以及被 system class loader 所加载的那些对象。堆空间中的每个对象都是由一个根元素为起点被层层调用的。因此,一个对象还被某一个存活的根元素所引用,就会被认为是存活对象,不能被回收,进行内存释放。因此,我们可以通过分析一个对象到根元素的引用路径来分析为什么该对象不能被顺利回收。如果说一个对象已经不被任何程序逻辑所需要但是还存在被根元素引用的情况,我们可以说这里存在内存泄露。
于是,我们进一步进行分析,点击下面的Details连接。
可以对象的详细分析报告:
我们查看下从 GC 根元素到内存消耗聚集点的最短路径:
我们可以很清楚的看到整个引用链,内存聚集点是一个拥有大量对象的集合,如果你对代码比较熟悉的话,相信这些信息应该能给你提供一些找到内存泄露的思路了。
接下来,我们再继续看看,这个对象集合里到底存放了什么,为什么会消耗掉如此多的内存。
在这张图上,我们可以清楚的看到,这个对象集合中保存了大量 HeapOOM对象的引用,就是它导致的内存泄露。
至此,我们已经拥有了足够的信息去寻找泄露点,回到代码,我们发现,是下面的代码导致了内存泄露 。
List<OOMObject> list = new ArrayList<OOMObject>();
while(true){
list.add(new OOMObject());
}
(3)Dominator Tree