通过mybatis源码,分析一次由mybatis使用不当导致的OutOfMemoryError的协查

    说是mybatis使用不当,其实也不准确,应该说是不了解mybatis的一些工作机制,下文带着大家一起分析一下mybatis源码,了解一下这次线上OOM原因.

    首先了解一下是什么OOM?

    OOM 是Out Of Memory 的简写,中文翻译:内存溢出. 百科上的说:内存溢出(out of memory)通俗理解就是内存不够,通常在运行大型软件或游戏时,软件或游戏所需要的内存远远超出了你主机内安装的内存所承受大小,就叫内存溢出。

    在生产环境中,内存不是无限大,内存的资源也很宝贵,而且生产环境中,每台服务器可能会部署多个应用,如果是用了虚拟技术或者容器技术的话,可能会一台服务器上部署上千应用,为了不让应用间内存使用相互影响,我们通常会限制每个应用jvm可使用的最大内存,当我们的应用需要的内存超出这个最大值就会内存溢出,导致整个用户挂掉,而不会无限申请内存,应用到其它应用.

    一般我们线上分配的内存都是合理评估后给出的,满足线上的使用需求,发生内存溢出,大多是由于代码写的不够好,短时间会占用过多内存,或是内存泄露(使用后的内存得不到释放,逐步累积增多)导致。

    分析OOM的工具也有很多,我一般常用eclipse的插件 Eclipse Memory Analyzer  接下来提供的一下截图也是基于Eclipse Memory Analyzer的。

前几天收到线上平台的告警,说是有一台Server发生了OOM,拿到了dump 文件开始分析:

可以看到有3处比较严重的疑似泄露的地方,用了近4.7G的内存.

再进到Details分析:

扫描二维码关注公众号,回复: 11835570 查看本文章

是Thread-1606 里的DefaultResultHandler里有一个ArrayList持有了暴多的Object, 这些都是我们的业务对象XXXInfo.

为什么会在这里存放那么多的XXInfo,再向下看:第一张图里的链接See stacktrace点进去

通个这个Thread Stack 我们就能快速的定位代码,没有我们代码里是有一个selectOne 的查询,返回XXXInfo对象,selectOne 代码里用的太多了,也用了那么长时间,也一直没啥问题,接我们就看了mybatis源码,

看org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne 方法 

原来selectOne 是调的selectList方法,把数据到到ArryList 再通过list.size做判断 ,我接着去日志里查看了一理下TooManyResultsException 果然有异常:

好嗨哦!3199254个对象,这下总算明白了:原来我们的sql本来是想查询一条数据,没成想查到了300w 而且mybatis的selectOne竟然是把这300w全放数组里,再返回异常,按理说,每次执行后,数据不再使用了,内存会被释放掉,也不应该会占用太多内存,但是mybatis有缓存啊!我们再看一下源码:

我们跟到后面发现,数据被放到了mybatis的localCache ,

PerpetualCache类型的永久缓存,保存在执行器中(BaseExecutor),一级缓存,又叫本地缓存

,god! 我去查了一日志,这个异常抛了十多次,那就是300w*10个对象,如果当时没溢出,估计还会有更多的查询报错,直到内存崩溃!到这里可以去修改代码了!

总结一下:这里最主要的问题还是在于,为什么selectOne 会返回多条的问题,其实这不能算是mybatis的锅,更多的是我们的业务逻辑有问题,或者数据有问题。通过修复代码逻辑,或者数据以上问题会得到有效解决。但是了解了mybatis的这个机制后,会帮助我们写出更健壮的代码!

猜你喜欢

转载自blog.csdn.net/kevin_mails/article/details/84982006