前段时间发现有一个内存溢出导致java线上服务OOM的问题,通过jvm启动配置增加-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/app/oom的命令,在出现了第二次服务挂掉的时候,拿到了当时的dump文件,以下是我通过dump文件对异常进行梳理的过程。

首先,把服务器的dump文件拉到我本地后,使用jdk自带的jvisualvm装载dump文件

java dump 文件 docker java dump文件太大_java dump 文件 docker

在载入后,我得到了各个类的内存占用情况分析图。如下:

java dump 文件 docker java dump文件太大_List_02

在这个分析图表中,我发现到了byte[]、Long、string、char[]都占用了极大的空间,char[]和string都还好解释,毕竟string底层就是char[]数组,并且string在项目中经常被人使用,但是也没道理占用那么多空间。

经过仔细思考,我百思不得其解,于是我决定继续往下看看其他类对象的空间占用,这咋一看没什么问题,但是我突然警醒,有两个很重要的mysql下的对象byteArryRow和mysqlTextValueDecoder,居然实例数占到了800000左右如此之多。而且,再往下看,有一个业务对象performResultVo和这两个mysql对象实例数极为接近,突然嗅到了一股不一样的味道。

java dump 文件 docker java dump文件太大_java dump 文件 docker_03

 当即,我跟踪了byteArryRow实例视图,突然发现,这个不就是结果集吗,用来把我们数据查询后的结果,遍历塞到我们的List里面。而且,这个byteArryRow不就是byte数组转换过来的吗,这个占用内存第一的byte[]不就挂勾上了吗。结合业务对象performResultVo和byteArryRow的实例数极其相近,我终于想通了,其实不就是byteArryRow在查询完数据后,将数据转为List<performResultVo>,而这个list的长度居然达到了800000条左右!!!

java dump 文件 docker java dump文件太大_java dump 文件 docker_04

为了印证我的猜想是否正确,我跟踪了performResultVo的实例视图,发现了,这个类下面的字段,里面包含了大量string和4个Long对象。我将performResultVo实例数*4得到了3181392个实例数,再比对Long的实例数,发现实例数量基本一模一样。这也印证了我的猜想,就是因为出现了海量数据的查找,导致数据库查询出来的resultSet不断长时间循环赋值和内存空间创建,并且无法释放内存,导致内存被占满,最后溢出。

java dump 文件 docker java dump文件太大_mysql_05

java dump 文件 docker java dump文件太大_mysql_06

 这次通过dump排查OOM异常的经历终于圆满结束了,写在末尾,我想说一点儿心得。在出现内存溢出时,首先,必须要保证线上系统的立即可用;其次,要及时通过各类命令获得OOM的dump文件;然后,要仔细分析dump文件,找到他们内联的关系,不要错过任何一个疑惑的点,你想知道的,dump文件都可以告诉你;最后通过定位到的异常点,顺藤摸瓜,找到出错问题点,加以印证。

最后,贴一个OOM异常的所有报错情况,引用自怡安 - 博客园这位小伙伴的文章。

java dump 文件 docker java dump文件太大_mysql_07