【jvm】记一次线上Java heap space内存溢出问题排查记录

最近项目上有一次服务突然不可用了,访问不到,于是通过日志排查到有大量的java.lang.OutOfMemoryError: Java heap space异常日志;


当在日志中看到内存溢出异常时,则最为头疼了,因为排查难度大,通常排查周期也长,通常是程序中的代码陷入了死循环或者是程序中产生了大量的大对象未及时回收,导致在多次GC后,内存资源依然紧张;


当看到日志中内存溢出时,首先我们要及时拿到hprof文件;hprof是一个每个JDK发行版都会内置的堆和CPU分析工具。它是一个使用JVM TI和JVM交互的DLL。该工具可以将分析信息写入文件或者以ASCII或者二进制格式写入socket,这些信息将来可以用前端分析工具来处理。


首先,我们获取当前运行程序的进程号pid,然后通过以下命令输出hprof文件

jmap -dump:live,format=b,file=/home/myheapdump.hprof 应用进程ID


这样就会在上述目录下生成对应的hprof文件,通常这个文件会比较大,大小1个G到10个G不等;生成完hprof文件之后,我们就可以将应用程序进行重启,使其暂时先回复到正常工作状态;


拿到hprof文件之后,我们就要开始对hprof文件进行分析了,查看具体是什么原因导致的内存溢出;有很多的工具可以分析hprof文件,这里我这边采用的是JDK自带的jvisualvm.exe这个工具;在jdk的安装目录的bin目录下有这个文件;比如我的路径为: C:\Java\jdk_8u_231\bin\jvisualvm.exe


双击运行jvisualvm.exe,在左侧菜单栏选择 文件 --> 装入 ,选择装入文件类型为 堆 Dump(*.hprof),找到下载好的hprof文件,打开它;


在装载过程中会有点耗时,取决于生成的hprof文件的大小;


在主控制台页面,选择 类,然后按实例数或者是大小进行排序,如下图


通过上图,可以看到 char[] 数组大小占用了94.3%,这个就太诡异了;正常的hprof确实也应该是char[]数组和String等基本数据类型占用最多,不过这个比例有点太高了;所以我这边断定应该是char[]哪个部分出问题了;


不同场景根据不同的方向去排查,如果char[]和String的数据量豆比较正常,这时候可以找找是否有那些带有项目包路径的类名,也可能是项目中的大对象导致内存溢出的;


由于char[]大小占用量太过异常,跟着char[]点进去看一下,在查看这些char[]的值的时候,发现好像有java代码中的一些日志信息,如下图所示:


可以看到上述的char[]中按大小排序后,内容都是 xssClean() - value 开头的,由于数组长度太长了,所以jvisualvm把内容截取了,选择 保存到文件,然后查看文件,发现一个文件就有2M多;于是那些这些特定字符去项目中搜索,发现是一个日志的输出:


有次可见,程序在这里每输出一个日志,就有2M的内存消耗,最终导致内存耗尽,出现了Java heap space日志;进一步排查代码,发现代码中有漏洞,导致xss过滤时陷入了循环,导致输出的日志出现问题;


经修改后重新上线,问题得以解决;


在项目中Java heap space问题会有很多中,有的排查起来特别困难,比如大对象之类的,不太好定位问题在哪,而且有的类资源占用情况不够明显,进一步增大了排查难度;有时候不太好定位问题所在时,只好修改jvm参数,调整内存分布,有时候能够解决一部分问题,但是如果是循环之类的问题造成的内存溢出,增大jvm可能也无法解决;

编辑于 2020-07-26 12:46