生產(chǎn)環(huán)境又 OOM 了,這次Mybatis 的鍋!
前言
繼上次線上CPU出現(xiàn)了報警,這次服務又開始整活了,風平浪靜了沒幾天,看生產(chǎn)日志服務的運行的時候,頻繁的出現(xiàn)OutOfMemoryError,就是我們俗稱的OOM,這可還行!
頻繁的OOM直接會造成服務處于一個不可用的情況,通過Skywalking查看鏈路調用,基本全報紅了,基本處于一個癱瘓狀態(tài),因為生產(chǎn)該服務是分布式部署,運維當即立斷對該服務進行重啟,因為是B端的產(chǎn)品,先讓公司業(yè)務能用起來了,保證服務的正常使用,然后緊急查看問題,當然這個問題就來到了我這里,既然分配給我了,咱高低給它查出來,并且修復了。
OutOfMemoryError出現(xiàn)的原因
先來了解下OutOfMemoryError出現(xiàn)的原因,無非就是兩類堆內存空間不足、元空間不足
-
堆內存空間不足:意味著程序存在一直有引用的對象(強引用),主要對象在引用的狀態(tài)就無法被GC回收,撐爆了-Xmx堆拓展的最大值,內存不足自然就會觸發(fā)堆內存溢出。
-
元空間:Java 8引入了元空間概念,代替了之前堆的永久代,由于元空間屬于堆外內存,不需要有對象引用,通過指針的方式表示類和元數(shù)據(jù),之所以引用元空間就是一種JDK的升級優(yōu)化,避免了永久代的內存溢出。
常見堆內存溢出的幾種情況
-
查詢數(shù)據(jù)庫返回的數(shù)據(jù)量過大,加載到內存中導致內存溢出;
-
代碼中出現(xiàn)死循環(huán)情況,導致大對象一直被引用不能被GC回收;
-
資源鏈接池、io流在使用完沒有進行手動釋放;
-
靜態(tài)集合類里面存在引用對象,始終存在引用關系,沒有進行清除;
以上屬于常見的幾種堆內存溢出的場景,當然有時候我們的遇到的問題都是稀奇古怪的問題,常見的問題總是很少能遇到…
現(xiàn)象分析
根據(jù)生產(chǎn)環(huán)境的報錯日志來看,這邊屬于Mybatis報出的一個內存溢出情況,通過去看Mybatis源碼發(fā)現(xiàn),底層也是通過一些集合類來存放拼接的sql,那么當然也有可能出現(xiàn)堆內存溢出,而且在sql體積比較大的情況下,接收sql的集合就會變的非常大,如果回收不了那么就會導致內存溢出。
由于我們docker容器里面沒有一些jstack、jmap的工具,并且dump文件也沒有進行保存…導致我無法通過看線程高占用內存的對象,來分析具體是什么操作發(fā)生的內存溢出,這就難了…
于是只能去網(wǎng)上搜搜看了,沒想到真的給到我一些啟發(fā),并且有點思路大概知道是哪里的問題。
文章來源于zzzzbw作者寫一篇關于 慘遭DruidDataSource和Mybatis暗算,導致OOM[1] ,很感謝??這位作者給我?guī)淼膯l(fā)。
文章作者也遇到了Mybatis帶來的OOM,主要是因為Mybatis拼接SQL的時候生成的占位符和參數(shù)對象,存放在Map里,當SQL的參數(shù)多導致SQL太長的時候,Map持有這些SQL時間較長,并且多線程同時操作,這時候內存占用就很高,從而發(fā)生OOM
Mybatis源碼分析
通過對DynamicContext類源碼查看,DynamicContext又一個ContextMap 類型的參數(shù)bindings,繼承了HashMap相當于一個Map集合,接著看這個類中的getBindings方法,看到了ForEachSqlNode這類調用了getBindings方法,簡單的說就是ForEachSqlNode通過getBindings方法,將SQL參數(shù)和參數(shù)的占位符統(tǒng)一put到ContextMap這個集合里面,主要是這里面的參數(shù)和占位符無法被GC回收,并發(fā)查詢量多的情況下就會導致OOM。
情景復現(xiàn)
隨后我做了線上場景的復現(xiàn),通過將SQL語句的拼接,將IN里面的參數(shù)變大,然后創(chuàng)建50個線程進行執(zhí)行,將JVM堆內存設為-Xmx256m -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError
這里看控制臺打印的日志,服務在頻繁的進行Full GC,導致OOM。
總結
既然發(fā)現(xiàn)了問題出現(xiàn)的原因,接下來就是對代碼SQL進行優(yōu)化,盡量避免在sql拼接的時候體積過大,這里告誡我們代碼不能亂寫,SQL語句也不能隨意寫啊,有時候把問題想的過于簡單確實會帶來不可預知的風險。
參考資料
慘遭DruidDataSource和Mybatis暗算,導致OOM: https://segmentfault.com/a/1190000021636834
