• 记一次dump文件分析历程


    一、背景

    今天下午,正酣畅淋漓的搬砖,突然运维同事在群里通知,核心服务某个节点内存异常,服务假死。神经一下子紧张起来,赶紧跑到运维那边观察现象。

    观察的结果是服务内存溢出,该服务是核心服务,分配了5G内存。运维在转存快照后,立刻重启服务后正常。在接下来的一段时间里,另一台服务节点也发生了同样的情况。

    二、分析过程

    这个服务是另外一个同事负责开发的,本着学习的态度,在拿到运维转存的dump文件后,就准备尝试着分析下问题,由于之前没有类似的经历,于是先在网上查了下一般怎么分析类似的问题。

    首先尝试使用MAT(Memory Analyzer)工具进行分析,下载后就准备载入dump文件,很不幸由于dump文件过大,载入失败了,于是调大了内存大小,尝试再次载入,但此时这个文件不再尝试重新载入,直接提示载入失败。

    先不纠结工具的问题,然后网上说JDK自带的jvisualvm也可以用来分析dump文件, 但也遇到了同样内存不足的问题,再尝试修改jvisualvm的内存限制后, 成功载入了。

    看到的界面是这样的,很明显看到char[]占用了近70%的内存,接近4G,这太不正常了,点进去看对应的实例(加载的非常慢,需要耐心)。

    在实例数界面中看到实例数达到了千万级,大部分都是一些文件的路径字符串信息。在业务中,我们会生成很多临时文件,然后这些临时文件会删除,这里面大部分保存的是这些临时文件路径。

    到这里导致内存泄露的原因似乎找到了,但好像又还不够,是什么原因导致这些临时变量没有被回收呢。

    回到家后,还是想着这个事情,于是又开始研究起来,这个时候想起来可以再用MAT试着分析下,毕竟据说工具很强大。重启了电脑之后,经过漫长的等待,载入成功了(果然重启能解决一切问题)。

    MAT的界面是这样的,里面包含的信息比较多,对于我这个菜鸟来说,确实一下子不知道看哪里。
    那就一个个慢慢看吧,Histogram里面的与使用jvisualvm中看到的信息是相同的。

    接下来进入到Dominator Tree视图, 列出当前存活的对象的内存大小,这看起来像是我需要关注的重点。然后查了下这个类 java.io.DeleteOnExitHook 与 内存泄露的相关问题。

    这个问题在下面两个链接中给出了说明,大概意思是在删除文件使用 File.deleteOnExit() 方法时,并不是立刻删除文件,而是将该文件路径维护在类DeleteOnExit的一个LinkedHashSet中,最后在JVM关闭的时候,才会去删除这里面的文件,这个方法不能用于长时间运行的服务。
    https://stackoverflow.com/questions/40119188/memory-leak-on-deleteonexithook
    https://bugs.openjdk.java.net/browse/JDK-6664633

    上面的描述,通过源码和JDK文档也都得到了证明。

    // java.io.File
    // Requests that the file or directory denoted by this abstract pathname be deleted when the virtual machine terminates.
    public void deleteOnExit() {
    	SecurityManager security = System.getSecurityManager();
    	if (security != null) {
    		security.checkDelete(path);
    	}
    	if (isInvalid()) {
    		return;
    	}
    	DeleteOnExitHook.add(path);
    }
    
    // java.io.DeleteOnExitHook
    private static LinkedHashSet<String> files = new LinkedHashSet<>();
    
    static synchronized void add(String file) {
    	if(files == null) {
    		// DeleteOnExitHook is running. Too late to add a file
    		throw new IllegalStateException("Shutdown in progress");
    	}
    
    	files.add(file);
    }

    三、结论

    问题定位于File.deleteOnExit()方法的调用,导致内存泄漏。调用该方法只会将需要删除文件的路径,维护在类DeleteOnExit的一个LinkedHashSet中,在JVM关闭时,才会去真正执行删除文件操作。这样导致DeleteOnExitHook这个对象越来越大,最终内存溢出。

    File.delete()File.deleteOnExit() 的区别:
    当调用delete()方法时,直接删除文件,不管该文件是否存在,一经调用立即执行
    当调用deleteOnExit()方法时,只是相当于对deleteOnExit()作一个声明,当程序运行结束,JVM终止时才真正调用deleteOnExit()方法实现删除操作。

    我写了下面这个测试方法,对比 delete()deleteOnExit()的区别,现象会比较明显。使用deleteOnExit时是在文件全部创建,JVM关闭的时候,才一个个删除文件,delete会立刻删除文件。(所以这个方法的使用场景是怎样的,我就不太清楚了)

    public static void loopTest() throws IOException {
    	String root = "D:\\C_Temp\\files\\";
    
    	File path = new File(root);
    	if (!path.exists()) {
    		path.mkdirs();
    	}
    	int i = 0;
    	while (i < 40000) {
    		File file = new File(path, "Hello-" + i + ".txt");
    		file.createNewFile();
    		file.delete();
    //            file.deleteOnExit();
    		i++;
    	}
    }

    四、收获

    本次排查经历最大的收获就是尝试利用工具分析dump文件,以前对这种都是望而却步,感觉很难。但这次带着问题去分析、思考,这样下来也不算过于复杂。有些问题不是问题本身难,是自己把它想得很难。

    下面是本次的一些思考和踩过的坑,以作备忘。

    1. 获取dump文件有两种方法

    1)通过 jmap 工具生成可以生成任意Java进程的dump文件

    # 先找到PID
    ps -ef | grep java
    
    # jmap 转存快照
    jmap -dump:format=b,file=/opt/dump/test.dump {PID}

    2)通过配置JVM启动参数

    #  当程序出现OutofMemory时,将会在相应的目录下生成一份dump文件,如果不指定选项HeapDumpPath则在当前目录下生成dump文件
    -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/dumps

    2. MAT需要JDK11才能运行

    解决办法是,打开MAT的安装目录,有一个配置文件MemoryAnalyzer.ini。打开这个文件,在文件中指定JDK版本即可。新增两行配置:

    -vm D:/jdkPath/bin/javaw.exe

    3. 在使用jvisualvm分析大的dump文件时,堆查器使用的内存不足

    修改JAVA_HOME/lib/visualvm/etc/visualvm.conf文件中 visualvm_default_options="-J-client -J-Xms24 -J-Xmx256m",然后重启jvisualVM即可

    4. MAT修改内存空间

    分析堆转储文件需要消耗很多的堆空间,为了保证分析的效率和性能,在有条件的情况下,建议分配给 MAT 尽可能多的内存资源。两种方式分配内存资源给 MAT:
    1)修改启动参数 MemoryAnalyzer.exe -vmargs -Xmx4g
    2)编辑文件 MemoryAnalyzer.ini 添加 -vmargs – Xmx4g

    这里也列一个代办项

    • 学习MAT工具的使用

    参考的一些文章:


    __EOF__

  • 本文作者: Hans
  • 本文链接: https://www.cnblogs.com/hans-hu/p/15999074.html
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    SpringBoot2.0---------------5、SpringBoot自动配置原理
    mac常见问题(五) Mac 无法开机
    什么是Mybatis的一二级缓存?
    SpringBoot SSMP整合案例
    2022-08-06
    3d可视化智慧园区设计成本
    【力扣10天SQL入门】Day5+6 合并表
    快速删除B站的关注列表
    【Mybatis】浅谈延迟加载
    【踩坑专栏】Cannot resolve org.springframework.boot:spring-boot-starter-web:unknown
  • 原文地址:https://www.cnblogs.com/hans-hu/p/15999074.html