原文:https://juejin.cn/post/7221461552343072828
继上次线上 CPU 出现了报警,这次服务又开始整活了,风平浪静了没几天,看生产日志服务的运行的时候,频繁的出现 OutOfMemoryError,就是我们俗称的 OOM,这可还行!频繁的 OOM 直接会造成服务处于一个不可用的情况,通过 Skywalking 查看链路调用,基本全报红了,基本处于一个瘫痪状态,因为生产该服务是分布式部署,运维当即立断对该服务进行重启,因为是 B 端的产品,先让公司业务能用起来了,保证服务的正常使用,然后紧急查看问题,当然这个问题就来到了我这里,既然分配给我了,咱高低给它查出来,并且修复了。
先来了解下 OutOfMemoryError 出现的原因,无非就是两类堆内存空间不足、元空间不足
以上属于常见的几种堆内存溢出的场景,当然有时候我们的遇到的问题都是稀奇古怪的问题,常见的问题总是很少能遇到…
根据生产环境的报错日志来看,这边属于 Mybatis 报出的一个内存溢出情况,通过去看 Mybatis 源码发现,底层也是通过一些集合类来存放拼接的 sql,那么当然也有可能出现堆内存溢出,而且在 sql 体积比较大的情况下,接收 sql 的集合就会变的非常大,如果回收不了那么就会导致内存溢出。

由于我们 docker 容器里面没有一些 jstack、jmap 的工具,并且 dump 文件也没有进行保存…导致我无法通过看线程高占用内存的对象,来分析具体是什么操作发生的内存溢出,这就难了… 于是只能去网上搜搜看了,没想到真的给到我一些启发,并且有点思路大概知道是哪里的问题。
文章来源于 zzzzbw 作者写一篇关于 惨遭 DruidDataSource 和 Mybatis 暗算,导致 OOM[1] ,很感谢 🙏 这位作者给我带来的启发。文章作者也遇到了 Mybatis 带来的 OOM,主要是因为 Mybatis 拼接 SQL 的时候生成的占位符和参数对象,存放在 Map 里,当 SQL 的参数多导致 SQL 太长的时候,Map 持有这些 SQL 时间较长,并且多线程同时操作,这时候内存占用就很高,从而发生 OOM
通过对 DynamicContext 类源码查看,DynamicContext 又一个 ContextMap 类型的参数 bindings,继承了 HashMap 相当于一个 Map 集合,接着看这个类中的 getBindings 方法,看到了 ForEachSqlNode 这类调用了 getBindings 方法,简单的说就是 ForEachSqlNode 通过 getBindings 方法,将 SQL 参数和参数的占位符统一 put 到 ContextMap 这个集合里面,主要是这里面的参数和占位符无法被 GC 回收,并发查询量多的情况下就会导致 OOM。



随后我做了线上场景的复现,通过将 SQL 语句的拼接,将 IN 里面的参数变大,然后创建 50 个线程进行执行,将 JVM 堆内存设为-Xmx256m -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError


这里看控制台打印的日志,服务在频繁的进行 Full GC,导致 OOM。

既然发现了问题出现的原因,接下来就是对代码 SQL 进行优化,尽量避免在 sql 拼接的时候体积过大,这里告诫我们代码不能乱写,SQL 语句也不能随意写啊,有时候把问题想的过于简单确实会带来不可预知的风险。
惨遭 DruidDataSource 和 Mybatis 暗算,导致 OOM: https://segmentfault.com/a/1190000021636834
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70024922/viewspace-2967407/ ,如需转载,请注明出处,否则将追究法律责任。