深入分析JVM之MAT应用

最近拜读周晓明的《深入分析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

这里写图片描述

参考:使用 Eclipse Memory Analyzer 进行堆转储文件分析

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值