使用Memory Analyzer tool(MAT)分析内存泄漏(二)

时间:2023-01-13 08:39:50

转载自:http://www.blogjava.net/rosen/archive/2010/06/13/323522.html

前言的前言

写blog就是好,在大前提下可以想说什么写什么,不像投稿那么字字斟酌。上周末回了趟成都办事,所以本文来迟了。K117从达州经由达成线往成都方向走
的时候,发现铁路边有条河,尽管我现在也不知道其名字,但已被其深深的陶醉。河很宽且水流平缓,河边山丘森林密布,民房星星点点的分布在河边,河里偶尔些
小船。当时我就在想,在这里生活是多么的惬意,夏天还可以下去畅游一番,闲来无事也可垂钓。唉,越来越讨厌北漂了。

前言

使用Memory Analyzer tool(MAT)分析内存泄漏(一)中,我介绍了内存泄漏的前因后果。在本文中,将介绍MAT如何根据heap dump分析泄漏根源。由于测试范例可能过于简单,很容易找出问题,但我期待借此举一反三。
一开始不得不说说ClassLoader,本质上,它的工作就是把磁盘上的类文件读入内存,然后调用
java.lang.ClassLoader.defineClass方法告诉系统把内存镜像处理成合法的字节码。Java提供了抽象类
ClassLoader,所有用户自定义类装载器都实例化自ClassLoader的子类。system class
loader在没有指定装载器的情况下默认装载用户类,在Sun Java
1.5中既sun.misc.Launcher$AppClassLoader。更详细的内容请参看下面的资料。

准备heap dump

请看下面的Pilot类,没啥特殊的。

/**
 * Pilot class
 * @author rosen jiang
 */
package org.rosenjiang.bo;

public class Pilot{
    
    String name;
    int age;
    
    public Pilot(String a, int b){
        name = a;
        age = b;
    }
}

然后再看OOMHeapTest类,它是如何撑破heap dump的。

/**
 * OOMHeapTest class
 * @author rosen jiang
 */
package org.rosenjiang.test;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.rosenjiang.bo.Pilot;

public class OOMHeapTest {
    public static void main(String[] args){
        oom();
    }
    
    private static void oom(){
        Map<String, Pilot> map = new HashMap<String, Pilot>();
        Object[] array ];
        ; i++){
            String d = new Date().toString();
            Pilot p = new Pilot(d, i);
            map.put(i+"rosen jiang", p);
            array[i]=p;
        }
    }
}

是的,上面构造了很多的Pilot类实例,向数组和map中放。由于是Strong
Ref,GC自然不会回收这些对象,一直放在heap中直到溢出。当然在运行前,先要在Eclipse中配置VM参数
-XX:+HeapDumpOnOutOfMemoryError。好了,一会儿功夫内存溢出,控制台打出如下信息。

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid3600.hprof 使用Memory Analyzer tool(MAT)分析内存泄漏(二)
Heap dump file created [78233961 bytes in 1.995 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
使用Memory Analyzer tool(MAT)分析内存泄漏(二)使用Memory Analyzer tool(MAT)分析内存泄漏(二)使用Memory Analyzer tool(MAT)分析内存泄漏(二)使用Memory Analyzer tool(MAT)分析内存泄漏(二)使用Memory Analyzer tool(MAT)分析内存泄漏(二)
使用Memory Analyzer tool(MAT)分析内存泄漏(二)使用Memory Analyzer tool(MAT)分析内存泄漏(二)使用Memory Analyzer tool(MAT)分析内存泄漏(二)
使用Memory Analyzer tool(MAT)分析内存泄漏(二)使用Memory Analyzer tool(MAT)分析内存泄漏(二)

java_pid3600.hprof既是heap dump,可以在OOMHeapTest类所在的工程根目录下找到。

MAT安装

话分两头说,有了heap
dump还得安装MAT。可以在http://www.eclipse.org/mat/downloads.php选择合适的方式安装。安装完成后切换
到Memory Analyzer视图。在Eclipse的左上角有Open Heap
Dump按钮,按照刚才说的路径找到java_pid3600.hprof文件并打开。解析hprof文件会花些时间,然后会弹出向导,直接Finish
即可。稍后会看到下图所示的界面。

使用Memory Analyzer tool(MAT)分析内存泄漏(二)

MAT工具分析了heap
dump后在界面上非常直观的展示了一个饼图,该图深色区域被怀疑有内存泄漏,可以发现整个heap才64M内存,深色区域就占了99.5%。接下来是一
个简短的描述,告诉我们main线程占用了大量内存,并且明确指出system class
loader加载的"java.lang.Thread"实例有内存聚集,并建议用关键字"java.lang.Thread"进行检查。所以,MAT通
过简单的两句话就说明了问题所在,就算使用者没什么处理内存问题的经验。在下面还有一个"Details"链接,在点开之前不妨考虑一个问题:为何对象实
例会聚集在内存中,为何存活(而未被GC)?是的——Strong Ref,那么再走近一些吧。

使用Memory Analyzer tool(MAT)分析内存泄漏(二)

点击了"Details"链接之后,除了在上一页看到的描述外,还有Shortest Paths To the Accumulation
Point和Accumulated Objects部分,这里说明了从GC root到聚集点的最短路径,以及完整的reference
chain。观察Accumulated
Objects部分,java.util.HashMap和java.lang.Object[1000000]实例的retained
heap(size)最大,在上一篇文章中我们知道retained heap代表从该类实例沿着reference
chain往下所能收集到的其他类实例的shallow
heap(size)总和,所以明显类实例都聚集在HashMap和Object数组中了。这里我们发现一个有趣的现象,既Object数组的
shallow heap和retained heap竟然一样,通过Shallow and retained sizes
文可知,数组的shallow heap和一般对象(非数组)不同,依赖于数组的长度和里面的元素的类型,对数组求shallow
heap,也就是求数组集合内所有对象的shallow
heap之和。好,再来看org.rosenjiang.bo.Pilot对象实例的shallow
heap为何是16,因为对象头是8字节,成员变量int是4字节、String引用是4字节,故总共16字节。

使用Memory Analyzer tool(MAT)分析内存泄漏(二)

接着往下看,来到了Accumulated Objects by
Class区域,顾名思义,这里能找到被聚集的对象实例的类名。org.rosenjiang.bo.Pilot类上头条了,被实例化了290,325
次,再返回去看程序,我承认是故意这么干的。还有很多有用的报告可用来协助分析问题,只是本文中的例子太简单,也用不上。以后如有用到,一定撰文详细叙
述。

又是perm gen

我们在上一篇文章中知道,perm gen是个异类,里面存储了类和方法数据(与class loader有关)以及interned strings(字符串驻留)。在heap dump中没有包含太多的perm gen信息。那么我们就用这些少量的信息来解决问题吧。

看下面的代码,利用interned strings把perm gen撑破了。

/**
 * OOMPermTest class
 * @author rosen jiang
 */
package org.rosenjiang.test;

public class OOMPermTest {
    public static void main(String[] args){
        oom();
    }
    
    private static void oom(){
        Object[] array ];
        ; i++){
            String d = String.valueOf(i).intern();
            array[i]=d;
        }
    }
}

控制台打印如下的信息,然后把java_pid1824.hprof文件导入到MAT。其实在MAT里,看到的状况应该和
“OutOfMemoryError: Java heap space”差不多(用了数组),因为heap dump并没有包含interned
strings方面的任何信息。只是在这里需要强调,使用intern()方法的时候应该多加注意。

java.lang.OutOfMemoryError: PermGen space
Dumping heap to java_pid1824.hprof 使用Memory Analyzer tool(MAT)分析内存泄漏(二)
Heap dump file created [121273334 bytes in 2.845 secs]
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
使用Memory Analyzer tool(MAT)分析内存泄漏(二)使用Memory Analyzer tool(MAT)分析内存泄漏(二)使用Memory Analyzer tool(MAT)分析内存泄漏(二)使用Memory Analyzer tool(MAT)分析内存泄漏(二)使用Memory Analyzer tool(MAT)分析内存泄漏(二)
使用Memory Analyzer tool(MAT)分析内存泄漏(二)使用Memory Analyzer tool(MAT)分析内存泄漏(二)使用Memory Analyzer tool(MAT)分析内存泄漏(二)
使用Memory Analyzer tool(MAT)分析内存泄漏(二)使用Memory Analyzer tool(MAT)分析内存泄漏(二)

倒是在思考如何把class
loader撑破废了些心思。经过尝试,发现使用ASM来动态生成类才能达到目的。ASM(http://asm.objectweb.org)的主要作
用是处理已编译类(compiled
class),能对已编译类进行生成、转换、分析(功能之一是实现动态代理),而且它运行起来足够的快和小巧,文档也全面,实属居家必备之良品。ASM提
供了core API和tree API,前者是基于事件的方式,后者是基于对象的方式,类似于XML的SAX、DOM解析,但是使用tree
API性能会有损失。既然下面要用到ASM,这里不得不啰嗦下已编译类的结构,包括:
    1、修饰符(例如public、private)、类名、父类名、接口和annotation部分。
    2、类成员变量声明,包括每个成员的修饰符、名字、类型和annotation。
    3、方法和构造函数描述,包括修饰符、名字、返回和传入参数类型,以及annotation。当然还包括这些方法或构造函数的具体Java字节码。
    4、常量池(constant pool)部分,constant pool是一个包含类中出现的数字、字符串、类型常量的数组。

使用Memory Analyzer tool(MAT)分析内存泄漏(二)

已编译类和原来的类源码区别在于,已编译类只包含类本身,内部类不会在已编译类中出现,而是生成另外一个已编译类文件;其二,已编译类中没有注释;其三,已编译类没有package和import部分。
这里还得说说已编译类对Java类型的描述,对于原始类型由单个大写字母表示,Z代表boolean、C代表char、B代表byte、S代表
short、I代表int、F代表float、J代表long、D代表double;而对类类型的描述使用内部名(internal
name)外加前缀L和后面的分号共同表示来表示,所谓内部名就是带全包路径的表示法,例如String的内部名是java/lang/String;对
于数组类型,使用单方括号加上数据元素类型的方式描述。最后对于方法的描述,用圆括号来表示,如果返回是void用V表示,具体参考下图。

使用Memory Analyzer tool(MAT)分析内存泄漏(二)使用Memory Analyzer tool(MAT)分析内存泄漏(二)

下面的代码中会使用ASM core
API,注意接口ClassVisitor是核心,FieldVisitor、MethodVisitor都是辅助接口。ClassVisitor应该按
照这样的方式来调用:visit visitSource? visitOuterClass? ( visitAnnotation |
visitAttribute )*( visitInnerClass | visitField | visitMethod )*
visitEnd。就是说visit方法必须首先调用,再调用最多一次的visitSource,再调用最多一次的visitOuterClass方法,
接下来再多次调用visitAnnotation和visitAttribute方法,最后是多次调用visitInnerClass、
visitField和visitMethod方法。调用完后再调用visitEnd方法作为结尾。

注意ClassWriter类,该类实现了ClassVisitor接口,通过toByteArray方法可以把已编译类直接构建成二进制形式。由于我们要动态生成子类,所以这里只对ClassWriter感兴趣。首先是抽象类原型:

/**
 * @author rosen jiang
 * MyAbsClass class
 */
package org.rosenjiang.test;

public abstract class MyAbsClass {
    ;
    ;
    ;
    abstract int absTo(Object o);
}

其次是自定义类加载器,实在没法,ClassLoader的defineClass方法都是protected的,要加载字节数组形式(因为toByteArray了)的类只有继承一下自己再实现。

/**
 * @author rosen jiang
 * MyClassLoader class
 */
package org.rosenjiang.test;

public class MyClassLoader extends ClassLoader {
    public Class defineClass(String name, byte[] b) {
        , b.length);
    }
}

最后是测试类。

/**
 * @author rosen jiang
 * OOMPermTest class
 */
package org.rosenjiang.test;

import java.util.ArrayList;
import java.util.List;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;

public class OOMPermTest {
    public static void main(String[] args) {
        OOMPermTest o = new OOMPermTest();
        o.oom();
    }

private void oom() {
        try {
            ClassWriter cw );
            cw.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC + Opcodes.ACC_ABSTRACT,
            "org/rosenjiang/test/MyAbsClass", null, "java/lang/Object",
            new String[] {});
            cw.visitField(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_STATIC, "LESS", "I",
            )).visitEnd();
            cw.visitField(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_STATIC, "EQUAL", "I",
            )).visitEnd();
            cw.visitField(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_STATIC, "GREATER", "I",
            )).visitEnd();
            cw.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_ABSTRACT, "absTo",
            "(Ljava/lang/Object;)I", null, null).visitEnd();
            cw.visitEnd();
            byte[] b = cw.toByteArray();

List<ClassLoader> classLoaders = new ArrayList<ClassLoader>();
            while (true) {
                MyClassLoader classLoader = new MyClassLoader();
                classLoader.defineClass("org.rosenjiang.test.MyAbsClass", b);
                classLoaders.add(classLoader);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

不一会儿,控制台就报错了。

java.lang.OutOfMemoryError: PermGen space
Dumping heap to java_pid3023.hprof 使用Memory Analyzer tool(MAT)分析内存泄漏(二)
Heap dump file created [92593641 bytes in 2.405 secs]
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
使用Memory Analyzer tool(MAT)分析内存泄漏(二)使用Memory Analyzer tool(MAT)分析内存泄漏(二)
使用Memory Analyzer tool(MAT)分析内存泄漏(二)

打开java_pid3023.hprof文件,注意看下图的Classes: 88.1k和Class Loader: 87.7k部分,从这点可看出class loader加载了大量的类。

使用Memory Analyzer tool(MAT)分析内存泄漏(二)

更进一步分析,点击上图中红框线圈起来的按钮,选择Java Basics——Class Loader
Explorer功能。打开后能看到下图所示的界面,第一列是class loader名字;第二列是class loader已定义类(defined
classes)的个数,这里要说一下已定义类和已加载类(loaded classes)了,当需要加载类的时候,相应的class
loader会首先把请求委派给父class loader,只有当父class loader加载失败后,该class
loader才会自己定义并加载类,这就是Java自己的“双亲委派加载链”结构;第三列是class loader所加载的类的实例数目。

使用Memory Analyzer tool(MAT)分析内存泄漏(二)

在Class Loader Explorer这里,能发现class loader是否加载了过多的类。另外,还有Duplicate Classes功能,也能协助分析重复加载的类,在此就不再截图了,可以肯定的是MyAbsClass被重复加载了N多次。

最后

其实MAT工具非常的强大,上面故弄玄虚的范例代码根本用不上MAT的其他分析功能,所以就不再描述了。其实对于OOM不只我列举的两种溢出错误,还有多
种其他错误,但我想说的是,对于perm
gen,如果实在找不出问题所在,建议使用JVM的-verbose参数,该参数会在后台打印出日志,可以用来查看哪个class
loader加载了什么类,例:“[Loaded org.rosenjiang.test.MyAbsClass from
org.rosenjiang.test.MyClassLoader]”。
全文完。

参考资料

memoryanalyzer Blog
java类加载器体系结构
ClassLoader

使用Memory Analyzer tool(MAT)分析内存泄漏(二)的更多相关文章

  1. 使用Memory Analyzer tool&lpar;MAT&rpar;分析内存泄漏

    前言的前言 写blog就是好,在大前提下可以想说什么写什么,不像投稿那么字字斟酌.上周末回了趟成都办事,所以本文来迟了.K117从达州经由达成线往成都方向走的时候,发现铁路边有条河,尽管我现在也不知道 ...

  2. 使用Memory Analyzer tool&lpar;MAT&rpar;分析内存泄漏(一)

    转载自:http://www.blogjava.net/rosen/archive/2010/05/21/321575.html 前言 在平时工作过程中,有时会遇到OutOfMemoryError,我 ...

  3. Memory Analyzer tool&lpar;MAT&rpar;分析内存泄漏---理解Retained Heap、Shallow Heap、GC Root

    Shallow Heap Size 指对象自身所占用的内存大小,不包含其引用的对象所占的内存大小. 1.数组类型 数组元素对象所占内存的大小总和. 2.非数组类型 对象与它所有的成员变量大小的总和.当 ...

  4. 性能监控 &vert; MAT分析内存泄漏

    使用MAT分析内存泄漏(二)八周年重印版 - 知乎 .u-safeAreaInset-top { height: constant(safe-area-inset-top) !important; h ...

  5. 【转】如何使用MAT分析内存泄漏

    原文链接:http://www.lightskystreet.com/2015/09/01/mat_usage/ MAT - Memory Analyzer Tool 使用进阶 Sep 1, 2015 ...

  6. &lbrack;原创&rsqb;Eclipse Memory Analyzer tool&lpar;MAT&rpar;工个使用介绍

    [原创]Eclipse Memory Analyzer tool(MAT)工个使用介绍

  7. 使用MAT&lpar;Memory Analyzer Tool&rpar;工具分析dump文件--转

    原文地址:http://gao-xianglong.iteye.com/blog/2173140?utm_source=tuicool&utm_medium=referral 前言 生产环境中 ...

  8. Android Studio &plus;MAT 分析内存泄漏实战

    对于内存泄漏,在Android中如果不注意的话,还是很容易出现的,尤其是在Activity中,比较容易出现,下面我就说下自己是如何查找内存泄露的. 首先什么是内存泄漏? 内存泄漏就是一些已经不使用的对 ...

  9. 使用go tool pprof分析内存泄漏、CPU消耗

    go中提供了pprof包来做代码的性能监控,在两个地方有包: net/http/pprof runtime/pprof 其实net/http/pprof中只是使用runtime/pprof包来进行封装 ...

随机推荐

  1. js调试--查找dom对象绑定的函数

    点击最右侧的js文件. 选中函数upload_pic_box,右击,选择在控制台中调试,或者在控制台直接输入该函数 点击最后一行代码会打开该函数所在的js文件

  2. 通过MD5排除重复文件

    今天下载了好多美女图片壁纸,可是看了一下发现好多图片重复了,有强迫症的我必须把重复的都给剔除掉,首先想到的当然是百度了,问问度娘有没有什么图片去重的工具,搜了一下还真有.奈何本人智商捉急用不来这高级的 ...

  3. SqlSever基础 print 在消息中输出hello world

    镇场诗:---大梦谁觉,水月中建博客.百千磨难,才知世事无常.---今持佛语,技术无量愿学.愿尽所学,铸一良心博客.------------------------------------------ ...

  4. ps做gif 登陆下拉菜单效果

    作者这里仅介绍登录动画的制作思路和简单过程.一些细节的制作,如登录框,每一帧的图像等都需要自己根据参考图慢慢完成.最终效果 1.新建大小适当的文件,背景填充暗蓝色.首先设计一个底座,主要用图层样式来完 ...

  5. mysql 连接问题----转载

    最近碰到一个mysql5数据库的问题.就是一个标准的servlet/tomcat网络应用,后台使用mysql数据库.问题是待机一晚上后,第二天早上第一次登录总是失败.察看日志发现如下错误: “com. ...

  6. Spring事务管理的实现方式:编程式事务与声明式事务

    1.上篇文章讲解了Spring事务的传播级别与隔离级别,以及分布式事务的简单配置,点击回看上篇文章 2.编程式事务:编码方式实现事务管理(代码演示为JDBC事务管理) Spring实现编程式事务,依赖 ...

  7. 【整合】input标签JS改变Value事件处理方法

    某人需要在时间控件给文本框赋值时,触发事件函数.实现的效果: 1.文本框支持手工输入,通过用户输入修改值,手工输入结束后触发事件.阻塞在于失去焦点后才触发(输入过程中不触发事件) 2.通过JS方法修改 ...

  8. &lbrack;USACO09FEB&rsqb;庙会班车Fair Shuttle

    题目描述 逛逛集市,兑兑奖品,看看节目对农夫约翰来说不算什么,可是他的奶牛们非常缺乏锻炼——如果要逛完一整天的集市,他们一定会筋疲力尽的.所以为了让奶牛们也能愉快地逛集市,约翰准备让奶牛们在集市上以车 ...

  9. java并发包java&period;util&period;concurrent详解

    线程池ThreadPoolExecutor的使用 并发容器之CopyOnWriteArrayList 并发容器之CopyOnWriteArraySet 数据结构之ConcurrentHashMap,区 ...

  10. ReentrantLock学习

    对于并发工作,你需要某种方式来防止两个任务访问相同的资源,至少在关键阶段不能出现这种冲突情况.防止这种冲突的方法就是当资源被一个任务使用时,在其上加锁.在前面的文章--synchronized学习中, ...