Performance Tuning -- 03 -- jmap+MAT 分析内存溢出

原文链接:Performance Tuning – 03 – jmap+MAT 分析内存溢出


相关文章:


这里我们通过 jmap 和 MAT 来分析下内存溢出的问题


一、堆内存溢出示例

  • User.java

    @Data
    public class User {
    
        private int id;
        private String name;
    
        public User(int id, String name) {
            this.id = id;
            this.name = name;
        }
    }
    
  • MemoryController.java

    @RestController
    public class MemoryController {
    
        private List<User> userList = new ArrayList<>();
    
        /**
         * -Xms64M -Xmx64M
         */
        @GetMapping(value = "/heap")
        public void heap() {
            AtomicInteger i = new AtomicInteger(0);
            while (true) {
                userList.add(new User(i.getAndIncrement(), UUID.randomUUID().toString()));
            }
        }
    }
    
  • 如上所示,在 Controller 层里,我们编写了一个 heap() 方法,在其内部无限循环地往 userlist 中添加 User 对象,直至内存溢出 (为了快速达到效果,这里我们将初始堆大小 (-Xms) 和最大堆大小 (-Xmx) 都设置为 64 M)

  • 运行项目后,我们通过浏览器来调用该接口,直至内存溢出
    在这里插入图片描述


二、非堆内存溢出示例

  • pom.xml

    <dependency>
        <groupId>org.ow2.asm</groupId>
        <artifactId>asm</artifactId>
        <version>7.1</version>
    </dependency>
    
  • ClassUtil.java

    public class ClassUtil extends ClassLoader {
    	
        public static List<Class<?>> createClasses() {
            List<Class<?>> classes = new ArrayList<>();
            // 循环1000w次生成1000w个不同的类。
            for (int i = 0; i < 10000000; ++i) {
                ClassWriter cw = new ClassWriter(0);
                // 定义一个类名称为Class{i},它的访问域为public,父类为java.lang.Object,不实现任何接口
                cw.visit(Opcodes.V1_1, Opcodes.ACC_PUBLIC, "Class" + i, null,
                        "java/lang/Object", null);
                // 定义构造函数<init>方法
                MethodVisitor mw = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>",
                        "()V", null, null);
                // 第一个指令为加载this
                mw.visitVarInsn(Opcodes.ALOAD, 0);
                // 第二个指令为调用父类Object的构造函数
                mw.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object",
                        "<init>", "()V");
                // 第三条指令为return
                mw.visitInsn(Opcodes.RETURN);
                mw.visitMaxs(1, 1);
                mw.visitEnd();
                ClassUtil test = new ClassUtil();
                byte[] code = cw.toByteArray();
                // 定义类
                Class<?> exampleClass = test.defineClass("Class" + i, code, 0, code.length);
                classes.add(exampleClass);
            }
            return classes;
        }
    }
    
  • MemoryController.java

    @RestController
    public class MemoryController {
    
        private List<Class<?>> classList = new ArrayList<>();
    
        /**
         * -XX:MetaspaceSize=64M -XX:MaxMetaspaceSize=64M
         */
        @GetMapping(value = "/nonheap")
        public void nonheap() {
            while (true) {
                classList.addAll(ClassUtil.createClasses());
            }
        }
    }
    
  • 如上所示,在 Controller 层里,我们编写了一个 nonheap() 方法,在其内部无限循环地往 classList 中添加 Class 对象,直至内存溢出 (为了快速达到效果,这里我们将元空间大小 (-XX:MetaspaceSize) 和最大元空间大小 (-XX:MaxMetaspaceSize) 都设置为 32 M)

  • 运行项目后,我们通过浏览器来调用该接口,直至内存溢出
    在这里插入图片描述


三、导出内存映像文件

  • 使用 -XX 命令自动导出

    • 通过 -XX:+HeapDumpOnOutOfMemoryError-XX:HeapDumpPath 命令来进行导出

      • -XX:+HeapDumpOnOutOfMemoryError

        • 当发生内存溢出时,自动导出堆转储快照 (dump 文件)
      • -XX:HeapDumpPath

        • 当发生内存溢出时,导出堆转储快照 (dump 文件) 至指定路径
      • 当内存较大时,自动导出可能会失败

  • 使用 jmap 命令手动导出

    • 通过 jmap -dump:format=b,file=heap.hprof <pid> 命令来进行导出
  • 这里我们针对堆内存溢出,采用第二种方式来手动导出堆转储快照 (dump 文件)
    在这里插入图片描述


四、使用 MAT 分析内存溢出

  • 首先我们需要先下载一个 MAT (Eclipse Memory Analyzer Open Source)
    在这里插入图片描述

  • 下载好之后,启动 MAT,并打开之前生成的 heap.hprof 文件,生成内存溢出分析报告
    在这里插入图片描述
    在这里插入图片描述

    • 如上所示,报告显示,a 区域占用了 93.57% 的内存,很明显的存在问题,我们可以点击 Details 来查看详细信息
  • 这里我们不通过点击 Details 来分析,而是点击左上角的第二个图标
    在这里插入图片描述

    • 如上所示,通过输入包名来查找我们自己定义的类,我们可以很清晰地看到存在 769575 个 User 对象,占用了将近 100 M 内存,很明显存在问题

      属性含义
      Class Name类名
      Objects类的对象数量
      Shallow Heap当前对象本身占据的内存大小,不包含其引用的对象
      Retained Heap当前对象本身占据的内存大小 + 当前对象可直接或间接引用到的对象占据的内存大小
  • 接着我们右击 User,来查看是谁引用了 User (只看强引用)
    在这里插入图片描述

  • 进入页面之后,我们一级一级地往下展开,最后看到了我们熟悉的 userList 和 User
    在这里插入图片描述

  • 至此,我们就定位到了内存溢出发生的地方,不过在实际生产环境中,造成内存溢出的问题可能会相对复杂,需要进行仔细分析才能解决


五、参考资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值