一、背景
某天还是按照原有的迭代计划,正常迭代项目。在测试环境测试没有问题之后到了发布上线的流程。可是到了上线那天晚上当应用刚发布的上去容器疯狂报警内存使用过高。短时间内容器从四个直接扩容到了十个。意识到事情不对赶紧执行了回滚。
二、过程分析
- 回滚之后让运维同学帮忙把内存的dump搞下来。使用jvisualvm打开内存镜像得到如下的分析结果
看到这个错误第一时间想到的是使用Mybatis批量的执行insert的数据量太大导致了内粗的溢出。在谷歌上看了看也有这样的说法。原来是3000一批的写入数据。使用二分的方式不断的缩小数据的量。到325的时候还是会出现内存溢出问题。那这个时候猜测不是由于这个原因引起的。 - 无奈之下只能再一次去dump的实例格式,如下:
我靠这个对象咋会有四十多万个。我一批在3000个就算是写入的时候事务没有提交。也需要一百多次才能到这个数量。在代码中排查了一波没有和事务相关的操作以及配置。由事务引起的可能性极小。当我们在方法中使用对象的时候,在栈帧中保存的只是对象的内存的地址。那出现这个原因就是在方法中一直创建对象然后对象一直被其他引用着没法被回收。定位到insert的代码如下:
看到这儿心里明白是为啥了:在应用启动的时候会启动的时候会向线程池提交多个任务去消费队列中的数据,同时呢在每一个任务中都是使用自旋的方式去队列中获取数据。但是呢将数据拿到之后放入List中等待下一步操作。问题就出在这儿。一直放入数据没有及时的清除已经处理过的数据。导致堆内存不断的被消耗,当MyBatis需要执行拷贝数据的逻辑的时候的内存空间不足就出现了OOM。其实根本的原因是堆内存被占满了。既然找到原因了那解决方法也很简单在处理完之后清除已经处理的数据。
三、总结反思
- 使用容器的时候,在使用完成之后一定要记得清理或者置空。
- 排查的方法不对导致浪费了一段时间。其实当看到是线程内部引发的内存溢出应该直接定位到写入数据的代码块。