记录一次Java内存溢出排查过程
这两天公司的一个程序出现问题,频繁出现内存溢出错误OutOfMemory:GC overhead limit exceeded.
虽然知道这个错误的原因是因为Java虚拟机在频繁进行垃圾回收,使用了98%的时间进行垃圾回收,但是实际回收了不到2%的内存。但结合到代码中,还是无法知道为什么会出现这个问题。
程序的内存设置为3G,6G都不行,快的话10分钟就内存溢出。没有办法,只能给Java程序加上命令行
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:\mat.bin
加上之后,如果程序再发生内存溢出,就会在指定位置生成内存映像文件(PS:如果等内存溢出之后,再去手动用jmap生成内存映像文件,可能那时的内存已经释放完了,导出来只有一点点)。
等了几天,程序照常内存溢出,成功生成了内存映像文件。
下载映像文件,使用memory Analyzer软件进行分析。
首先下载memory Analyzer,下载官网是https://www.eclipse.org/downloads/download.php?file=/mat/1.10.0/rcp/MemoryAnalyzer-1.10.0.20200225-win32.win32.x86_64.zip
下载页面可以选服务器,记得选国内的服务器。
下载后解压,记得修改MemoryAnalyzer.ini,把最大内存改大一点,不然都不够mat.bin内存映像文件用。
打开MemoryAnalyzer.exe,打开内存映像文件
打开后,可以看到,整个程序的大概内存情况,大概用了2.8G,鼠标放上去,显示其中单个类关联的大小就占了2.7个G
单击饼图,出现菜单,点击List objects -> with outging references
意思是查看这个类里面包含的对象
在这个页面中,点击第三列Retained Heap,让它从大到小排序
可以看到,其中的<Java Local> aaa@qq.com这个变量占用了2.8个G左右
<Java Local>表示是局部变量,也就是线程栈帧中的变量,而不是类的成员变量。
也就是说明,这个ArrayList是在某个方法中生成的,这个方法是关联到AutoReForwardThread线程。
点开ArrayList,可以看到每个元素的类型是什么,还有总共有4万多个元素,元素的大小有96k
查看这些元素里面的值,大概知道对应的业务模块是什么,然后找到代码,看看是否有一个局部变量List,保存了这些对象
最后找到这里,这里从数据库中查询出数据,放到list中
最后验证了数据库,确实查出来几十万条数据,从而导致了Java内存溢出。
总结:List list局部变量在ClassA.method1()中定义,该方法在ThreadB中进行调用,那么list能关联到ThreadB,而关联不到ClassA。所以需要深刻理解Java中的成员变量和局部变量的存放位置,才能明白内存印象文件中类、变量、局部变量、线程中的关联关系。