项目上在使用系统的某个功能时,系统报错,日志显示 OutOfMemoryError: Java heap space
内存溢出了,接下来就要分析原因了。从客户现场要来了错误日志、线程堆栈状态信息以及OOM时的JVM内存快照(可通过启动命令中加 -XX:+HeapDumpOnOutOfMemoryError ,在发生OOM时自动dump内存状态)。
问题排查:
首先通过错误日志定位到本次操作的是系统的数据导出功能的代码,该处代码是使用阿里的fastjson对自定义的一个对象转换成byte[]。
byte[] bytes = JSON.toJSONBytes(stuff, SerializerFeature.BrowserCompatible,
SerializerFeature.WriteClassName);
具体定位到fastjson内部代码位置,如下:
可以发现是在转换过程中,数组扩容导致的。询问客户运维,通过 jmap -heap pid 得到系统运行的内存设置,发现项目上设置的MaxHeapSize是3G,是符合我们的系统要求的。这是怎么回事呢? 这个问题在其他项目上也是从来没有出现过的,所以目前只好把这个作为初步原因,继续排查。
然后分析了堆栈信息,并没有发现异常情况。
最后来分析内存快照,使用Memory Analyzer Tool(MAT)打开了文件,但是分析后只是有两个较大的对象分别是50多M和20多M,看起来并不是这两个引起的。
然而同事使用 IBM HeapAnalyzer打开快照文件后, 发现其中有一个 1.7G的大数组!! 至于为什么两个工具分析的结果不同,目前也不知道原因(还是推荐使用MAT https://www.ibm.com/support/pages/ibm-heapanalyzer)。不过通过这个大数组和前面报错代码位置,基本可以确定就是在数组扩容时内存溢出了。
然后本人通过使用jdk自带工具jmc.exe(也可以用jvisualvm),在开发环境调试了下,监控内存,发现在进行本文一开始那种导出操作时,确实会出现占用内存激增的情况。
接下来解决问题,试了下用IO流导出,也会内存激增,不过导出文件相对小一些,所以占用内存也会小一些,但会导致导出文件失去可读性。分析客户的导入文件,发现其中大部分信息是不必要信息,所以优化导出逻辑,去除掉部分无用历史数据的导出,使导出文件变小,从而解决导入时的OOM问题(也可通过调大MaxHeapSize解决)。
以上。