<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          生產(chǎn)環(huán)境又 OOM 了,這次Mybatis 的鍋!

          共 2283字,需瀏覽 5分鐘

           ·

          2023-11-09 17:14

          前言

          繼上次線上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)的原因,無非就是兩類堆內存空間不足、元空間不足

          1. 堆內存空間不足:意味著程序存在一直有引用的對象(強引用),主要對象在引用的狀態(tài)就無法被GC回收,撐爆了-Xmx堆拓展的最大值,內存不足自然就會觸發(fā)堆內存溢出。

          2. 元空間:Java 8引入了元空間概念,代替了之前堆的永久代,由于元空間屬于堆外內存,不需要有對象引用,通過指針的方式表示類和元數(shù)據(jù),之所以引用元空間就是一種JDK的升級優(yōu)化,避免了永久代的內存溢出。

          常見堆內存溢出的幾種情況

          1. 查詢數(shù)據(jù)庫返回的數(shù)據(jù)量過大,加載到內存中導致內存溢出;

          2. 代碼中出現(xiàn)死循環(huán)情況,導致大對象一直被引用不能被GC回收;

          3. 資源鏈接池、io流在使用完沒有進行手動釋放;

          4. 靜態(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語句也不能隨意寫啊,有時候把問題想的過于簡單確實會帶來不可預知的風險。

          參考資料

          [1]

          慘遭DruidDataSource和Mybatis暗算,導致OOM: https://segmentfault.com/a/1190000021636834

          來源:juejin.cn/post/7221461552343072828


          瀏覽 688
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久草免费福利 | 91插插插插插插 | 日日夜夜撸撸 | 五月丁香中文字幕 | 成人簧片在线浏览观看 |