Shallow Size (对象自身占用的内存大小)

Retained Size (被GC后Heap上释放的内存大小)

with outgoing references(查看对象为什么消耗内存,查看对象引用的其他对象)

with incoming references(查看对象被谁引用)


MAT工具介绍

工欲善其事必先利其器,学会使用工具也是一种本领。本篇文章就把自己之前工作中用到的一个内存分析工具给大家介绍下。

内存分析工具MAT(Memory Analyzer Tool)是一款 JVM 的内存分析工具,在实际的工作中可以帮助我们解决生成上内存占用过高等问题。

我之前用 MAT 是在 eclipse上使用,前者是后者的一个插件。后来换到 IDEA 才知道原来 MAT 也有独立的可运行版本。它的下载地址如下:

http://www.eclipse.org/mat/downloads.php

测试代码

我们先准备一段简单的代码,这个代码会导致 JVM 堆内存溢出,方便我们演示 MAT 的效果。

public static void main(String[] args) throws InterruptedException {
        Map<String,Tom> map = new HashMap<String,Tom>();
        int counter = 1;
        while(true) {
            Thread.sleep(10);
            Tom tom = new Tom();
            String [] friends = new String[counter];
            for (int i = 0; i < friends.length; i++) {
                friends[i] = "friends"+i;
            }
            tom.setAge(counter);
            tom.setName("tom"+counter);
            tom.setFriends(friends);
            map.put(tom.getName(),tom);
            if(counter%100==0)
                System.out.println("put"+counter);
            counter++;
        }
    }

很好理解的一段代码,一个无限循环,不断的往map里添加名为Tom的对象,而且每次循环new出来的对象的friends属性还在不断的扩大。

然后我们使用启动下面这个启动参数运行代码,

-Xms200m -Xmx200m  -XX:+HeapDumpOnOutOfMemoryError

参数指定了堆内存大小是200m,这个大小我们的测试代码很快就会用完,然后报错。

启动代码,运行一段时间后报错如下,

java.lang.OutOfMemoryError: GC overhead limit exceeded
Dumping heap to java_pid1398.hprof ...
Heap dump file created [239632332 bytes in 0.865 secs]

从这个报错我们可以获取几个信息,首先是错误类型是内存溢出,原因是超出了GC的限制。

其次,我们看到程序出错时的内存快照 dump 到了一个名为 java_pid1398.hprof 的文件中了。这个文件就是可以用于 MAT 工具分析的dump文件。

除了上面的通过 -XX:+HeapDumpOnOutOfMemoryError 参数来dump内存文件之外(那么当有OutOfMemory异常出现的时候,JVM就会将当前的虚拟机的堆等信息放入hprof文件中,名字是java_pid加上进程号,比如:java_pid11656.hprof。), 还可以通过 jmap 命令来导出的内存快照。这里不做详述了。 

内存分析

我们现在根据 MAT 的分析,从几个维度来分析下代码中的问题。

MAT 工具打开前面的 dump 文件,会先看到下面这种图,

java Visualvm 怎么分析内存堆 java内存分析工具mat_jvm

 

从预览图,可以看到有个应用占用了总的堆内存的大部分,高达184M(程序运行分配的堆内存是200M)。说明这个应用肯定有问题,值得我们继续往下分析。

我们先看看工具给我们的一个判断,找到 Leak Suspects,点击去。

java Visualvm 怎么分析内存堆 java内存分析工具mat_使用_02

 从描述上看到,主线程有个本地变量占用了很大内存,这个变量是 HashMap 的实例。

哈哈,根据上面的代码,不得不说 MAT 还是很牛叉的,对于内存泄漏点定位的很准确。

不过有时候,我们还是需要手动分析下我们还是回到之前的预览页面,找到 Histogram 点进去,如下图:

java Visualvm 怎么分析内存堆 java内存分析工具mat_java_03

 

shallow heap 指的是对象自身占用的内存大小,不包括它引用的对象。 针对非数组类型的对象,它的大小就是对象与它所有的成员变量大小的总和。当然这里面还会包括一些java语言特性的数据存储单元。 针对数组类型的对象,它的大小是数组元素对象的大小总和。

我们看到排在前面的是占用内存比较多的, char[] 这个明显是String里引用造成的(string里用char[] 存储数据),我们之间来看String是被谁引用的,

 

java Visualvm 怎么分析内存堆 java内存分析工具mat_使用_04

 

java Visualvm 怎么分析内存堆 java内存分析工具mat_JVM_05

很清晰,Tom对象的friends属性消耗了很多内存。

这里说明下,

with outgoing references(查看对象为什么消耗内存,查看对象引用的其他对象)

with incoming references(查看对象被谁引用)

同理,我们可以继续分析下面几个类,最终都会定位到内存吃紧的原因是 hashmap 里短时间塞了大量的 Tom 对象撑爆了内存。