• MAT工具定位分析Java堆内存泄漏问题方法


    一、MAT概述与安装

    MAT,全称Memory Analysis Tools,是一款分析Java堆内存的工具,可以快速定位到堆内泄漏问题。该工具提供了两种使用方式,一种是插件版,可以安装到Eclipse使用,另一种是独立版,可以直接解压使用。

    我把独立版MAT安装包放到了网盘上,方便直接下载————
    链接:https://pan.baidu.com/s/1CG887mHBcnVq3RxOzmxRvA
    提取码:rhb5

    独立版解压后,其内部文件是这样的——

    image

    这里有一个MemoryAnalyzer.ini文件,里面有一个Xmx参数,默认是-Xmx1024m,这代表MAT的最大内存大小,根据具体分析的dump文件大小来做适当调整。

    点击MemoryAnalyzer.exe,启动完成后,即可以使用它来检查定位内存泄漏相关的问题了。

    image


    二、内存泄漏案例分析

    下面,我会结合一个小案例来分享MAT的使用。

    首先,用IDEA建立一个测试类——

    public class example {
        public static void main(String[] args)  {
            List<User> list=new ArrayList<>();
            while (true){
                list.add(new User());
            }
        }
    }
    
    class User {
        private String name="demo";
        public User() {
        }
    }
    
    

    给这个测试类设置虚拟机参数,设置如:-Xms2m -Xmx2m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:/local_system/git/demo/heapdump.hprof

    这几个参数的意义是:

    -Xms2m -Xmx2m:堆最小内存为2M,最大内存为2M。这里没有显示设置新生代大小,它会自动分配新生代大小,分配完剩下的,就是老年代大小了。

    -XX:+HeapDumpOnOutOfMemoryError:指发生内存溢出的时候,会自动生成一个二进制的堆快照文件,这个快照文件以.hprof后缀结尾。用MAT分析堆内存信息,就是利用这个.hprof文件。除了可以设置相应的虚拟机参数外,还可以通过jmap指令来获取到某个进程的堆快照文件,执行指令格式是:

    jmap -dump:format=b,file=<dumpfile.hprof> <pid>
    

    例如:jmap -dump:format=b,file=20210618.dump 7132,那么,这里20210618.dump就是自定义的dump堆转储文件名字,而7132是进程ID。只是使用jmap指令可能有一点不好的地方是,内存溢出是某个时间点发生的事情,jmap指令去获取到dump文件,存在时间差问题。而HeapDumpOnOutOfMemoryError则是在发生内存溢出时,同时生成的,故而会更准确些。

    -XX:HeapDumpPath=D:/local_system/git/demo/heapdump.hprof:内存溢出产生的堆快照自动存储路径,可以自定义指定路径。

    其实,在实际生产环境里,除了这些基本参数外,还有其他的JVM参数,这些参数都是用来调优的重点所在。

    这里暂且以这些参数做实验,在运行IDEA时,可以将这些参数设置在IDEA的“Run/Debug Configurations”弹出框的VM options输入框里,如下截图所示——

    image

    按照以上方式设置好后,就可以运行该案例代码了,运行一会儿后,就会出现以下提示——

    image

    这表明,该代码已经发生内存溢出了,即ArrayList存储的对象大小已经超过堆内存,导致无法进行垃圾回收,也就是出现内存泄漏,进而导致内存溢出。当然,在本地是可以看到这么简单的异常提示的,但是在线上服务器上,就没有那么明显的内存溢出提示,就需要获取到产生的堆快照dump文件,然后再进一步分析堆快照信息。


    三、使用MAT分析堆转储dump文件

    我们将这个heapdump.hprof文件导入到MAT里。启动MAT,点击File,选择Open Heap Dump,然后选择对应的hprof文件。 ![image](https://img2020.cnblogs.com/blog/1545382/202106/1545382-20210624185815985-572998625.png)

    在弹出框处,选择Leak Suspects Report,这是指内存泄漏报告——

    image

    点击Finish后,展示Overview主页面如下——

    image

    Overview主页面显示应用程序内存使用情况的概览,中间的饼图按retained size来显示最大的对象。注意一点是,在MAT中,会有两种大小表示,一个是Retained size,还有一个是Shallow Size,那么,两者有什么区别呢?

    • Shallow Size:表示对象自身占用的内存大小,不包括它引用的对象。
    • Retained size:当前对象内存大小+当前对象直接或间接引用的对象大小,全部的总和,简单理解,就是当前对象被GC后,总共能释放的内存大小。

    1.Details显示的是dump文件的情况,表示堆大小为1.1MB,有516个class,40.2k个Object,3个类加载器等;

    2.功能视图模块;

    3.报表模块;

    我比较喜欢用Actions的Histogram视图和Reports的Leak Suspects报表,Histogram视图是以类为维度来显示其实例数和每个类的使用内存量,可以协助我们查询哪些类对象占用较大内存;Leak Suspects则可以协助分析内存泄漏的原因所在。

    - Histogram视图

    image

    以Class Name为维度,分别展示各个类的对象数量,Shallow Size,Retained size。这里有一个疑惑是,Shallow Size和Retained size没有显示是以什么为单位的,它默认是以byte为单位的,若要显示地让单位展示出来,可以这样设置,点击Window->Preferences

    image

    选择最后一项,点击Apply and Close——

    image

    再重新打开Histogram视图,就会生效了,单位就显示出来了——

    image

    根据这个Histogram视图,我们可以发现,com.example.demo.User数量和占用内存大小都比较高,同时说明了该User对象一直没有被GC回收掉,这时,可以右击,弹出框有以下一些菜单选项——
    image

    • List objects

      使用List Object可以查看对象引用关系,这里查看引用功能,包括本对象引用外部对象with outgoing references与外部对象引用本对象with incoming references。

      1. with outgoing references

        使用该功能,可以查看对象内部都引用了哪些外部对象,例如,这里的User,其引用外部对象情况如下:

        image

        对照这个案例的代码,可见,在创建这个User对象时,内部属性name就会指向一个字符串地址,换言之,该User对象内部有个引用指向了一个name字符串地址。

        image

      2. with incoming references

      ​ 使用该功能,可以查看该对象都被哪些外部所引用了——

      image

    ​ 在案例代码当中,是以list.add(User)来不断存储User对象的,如截图所示,通过MAT可确定,存在一个ArrayList集合一直引用该User对象。

    在实际开发当中,一个对象可能引用了诸多其他外部对象或者被诸多外部对象所引用,若一直引用着,说明某个对象一直存在GC ROOT可达的情况,反过来就意味着,该被引用的对象一直无法被GC回收处理,那么就可能会一直存在堆内存里,进而造成内存泄漏的情况。

    • Merge Shortest Paths to GC Roots->exclude all phantom/weak/soft etc. references

    image

    排除其他引用,只观察GC路径上强引用的对象,所观察到的,都是仍存活的对象。

    除此之外,Histogram视图仍有其他功能,后期在学习过程当中,不断进行完善。

    - Leak Suspects报表

    image

    Leak Suspects报表很直观地展现了一个饼图,图中颜色深的部分表示可能存在内存泄漏的嫌疑。每一个模块都有对应的详情信息。

    这里拿模块a来讲解,其详情部分有一句话很关键:The memory is accumulated in one instance of "java.lang.Object[]", loaded by "", which occupies 617.55 KB (52.54%) bytes.The stacktrace of this Thread is available. See stacktrace. See stacktrace with involved local variables.

    这句话翻译过来就是,内存累积在一个“java.lang.Object[]”实例中,由“”加载,占用617.55 KB(52.54%)字节。此线程的堆栈跟踪可用。请参见stacktrace。请参阅包含局部变量的stacktrace。

    点击stacktrace,进入到一个页面,可以看到日志信息——

    image

    在这里,从下往上看异常信息,可以快速定位内存泄漏地方出现在哪个类方法里的哪行代码。

    我很喜欢使用这个功能,通过获取线上堆转储文件,便可以通过Leak Suspects定位到内存泄漏快速定位在哪一行代码。

    作者:朱季谦
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在文章页面给出原文链接,否则保留追究法律责任的权利。
  • 相关阅读:
    数列分段 II
    Best Cow Fences
    愤怒的牛
    linux 查看文件
    糖果传递
    BL刀片更换主板设置raid
    glance启停
    depot制作
    刀片服务器密码过期, console无法登录解决方案
    DP无法删除失效的多路径链路方法
  • 原文地址:https://www.cnblogs.com/zhujiqian/p/14928210.html
Copyright © 2020-2023  润新知