先列出几个基础的概念:html
Shallow Heap表示对象自己占用内存的大小,不包含对其余对象的引用,也就是对象头加成员变量(不是成员变量的值)的总和。java
Retained Heap是该对象本身的Shallow Heap,并加上从该对象能直接或间接访问到对象的Shallow Heap之和。换句话说,Retained Heap是该对象GC以后所能回收到内存的总和。正则表达式
把内存中的对象当作下图中的节点,而且对象和对象之间互相引用。这里有一个特殊的节点GC Roots,这就是reference chain的起点。缓存
从obj1入手,上图中蓝色节点表明仅仅只有经过obj1才能直接或间接访问的对象。由于能够经过GC Roots访问,因此左图的obj3不是蓝色节点;而在右图倒是蓝色,由于它已经被包含在retained集合内。因此对于左图,obj1的retained size是obj一、obj二、obj4的shallow size总和;右图的retained size是obj一、obj二、obj三、obj4的shallow size总和。obj2的retained size能够经过相同的方式计算。工具
对象引用按从最强到最弱有以下级别,不一样的引用(可到达性)级别反映了对象的生命周期:spa
JVM在进行GC的时候是经过使用可达性来判断对象是否存活,经过GC Roots(GC根节点)的对象做为起始点,从这些节点开始进行向下搜索,搜索所走过的路径成为Reference Chain(引用链),当一个对象到GC Roots没有任何引用链相连(用图论的话来讲就是从GC Roots到这个对象不可达)时,则证实此对象是不可用的。htm
以下图所示,对象object 五、object 六、object 7虽然互相有关联,它们的引用并不为0,可是它们到GC Roots是不可达的,所以它们将会被断定为是可回收的对象。对象
点击工具栏上的 图标能够打开Histogram(直方图)视图,能够列出每一个类产生的实例数量,以及所占用的内存大小和百分比。主界面以下图所示:排序
图中Shallow Heap 和 Retained Heap分别表示对象自身不包含引用的大小和对象自身并包含引用的大小,具体请参考下面 Shallow Heap 和 Retained Heap 部分的内容。默认的大小单位是 Bytes,能够在 Window - Preferences 菜单中设置单位,图中设置的是KB。生命周期
经过直方图视图能够很容易找到占用内存最多的几个类(经过Retained Heap排序),还能够经过其余方式进行分组(见下图)。
若是存在内存溢出,时间久了溢出类的实例数量或者内存占比会愈来愈多,排名也愈来愈靠前。能够点击工具类上的 图标进行对比,经过屡次对比不一样时间点下的直方图对比就很容易把溢出的类找出来。
还有一种对比直方图的方式,首先经过 Window 菜单打开 Navigation History 视图,选中直方图右键并选中 Add to Compare Basket项目,将直方图添加到 Compare Basket 中。
而后在 Compare Basket 中点击右上角的 按钮,能够分别列出对比的全部结果,见下图:
而且在上面的能够设置不一样的对比方式。
点击工具栏上的 图标能够打开Dominator Tree(支配树)视图,在此视图中列出了每一个对象(Object Instance)与其引用关系的树状结构,同时包含了占用内存的大小和百分比。
经过Dominator Tree视图能够很容易的找出占用内存最多的几个对象(根据Retained Heap或Percentage排序),和Histogram相似,能够经过不一样的方式进行分组显示:
Histogram视图和Dominator Tree视图的角度不一样,前者是基于类的角度,后者是基于对象实例的角度,而且能够更方便的看出其引用关系。
首先,在两个视图中找出疑似溢出的对象或者类(能够经过Retained Heap排序,而且能够在Class Name中输入正则表达式的关键词只显示指定的类名),而后右键选择Path To GC Roots(Histogram中没有此项)或Merge Shortest Paths to GC Roots,而后选择 exclude all phantom/weak/soft etc. reference:
GC Roots意为GC根节点,其含义见上面的 GC Roots和Reference Chain 部分,后面的 exclude all phantom/weak/soft etc. reference 意思是排除虚引用、弱引用和软引用,即只剩下强引用,由于除了强引用以外,其余的引用均可以被JVM GC掉,若是一个对象始终没法被GC,就说明有强引用存在,从而致使在GC的过程当中一直得不到回收,最终就内存溢出了。
经过结果就能够很方便的定位到具体的代码,而后分析是什么缘由没法释放该对象,好比被缓存了或者没有使用单例模式等等。
下面是执行的结果:
上图中保留了大量的VelocitySqlBulder的外部引用,后来查看了代码,原来每次调用的时候都实例化一个新的对象,因为VelocitySqlBulder类是无状态的工具类,所以修改成单例方式就能够解决这个问题。
根据上面分析的结果对问题进行处理以后,再对照以前的操做,看看对象是否还再持续增加,若是没有就说明这个地方的问题已经解决了。
最后再用 jstat 持续跟踪一段时间,看看Old和Perm区的内存是否最终稳定在一个范围以内,若是长时间稳定在一个范围说明溢出问题获得了解决,不然还要继续进行分析和处理,一直到稳定为止。
参考
原文连接:http://www.javatang.com