數據庫連接池配置錯誤導致OOM


一、背景介紹:

   運行在k8s集群中負責支付業務一個服務,運營一段時間就會被k8s kill,然后重啟, 通過查看k8s 的event發現系統達到了memory到達了上限被集群kill調。

   服務配置:jdk:1.8、堆內存:-Xmx800m -Xms800m 設置為800M,  k8s的memory.limit設置為1G。 

二、排查問題:

  1.初步分析: 由於系統的請求量不大,所以設置的堆內存足夠了,所以可以排除堆內存設置過小原因。同時由於服務被kill的原因是因為物理內存占用過大。所以懷疑是堆外內存溢出

   jvm內存結構分為:堆內存(新生代、老年代), 堆外內存(線程棧、元空間、直接內存)

  2.排查:

 

    2.1 gc狀態分析:jstat -gcutil pid 5s

    結果:各區域的占用情況,gc情況無明顯異常

 

    2.2 堆dump: jmap -dump:format=b,file=heap.hprof  pid, 使用mat分析如下

 

 

 

  很明顯 com.mysql.jdbc.NonRegisteringDriver占用堆內存的33%。其中java.util.concurrent.ConcurrentHashMap$Node[] 存在內存泄漏的可能。

ConcurrentHashMap<ConnectionPhantomReference, ConnectionPhantomReference> connectionPhantomRefs  保存mysql connection的虛引用。
當 mysql connection被釋放后,虛引用的refQueue中會收到釋放的connection。 定時清理線程會取出refQueue中保存的connection,然后將connection從 connectionPhantomRefs中清除。

  當查看詳細情況發現: connectionPhantomRefs 保存的連接個數600多個,而mysql的tomcat的連接池最大設置的max-active=100, 

 

 

 

 這說明了兩個問題:

  •  1. 有大部分的連接池沒有被回收
  •  2. 最大連接數max-active:100,而實際生成的卻遠遠大於max-active

2.3 連接池配置分析

   根據2.2中堆dump的情況發現了兩個問題:

  • (1) 有大部分的連接池沒有被回收
  • (2)最大連接數max-active:100,而實際生成的卻遠遠大於max-active,

  關於問題(1):通過jstat -gcutil pid 5s發現jvm old區占用50%,一直沒有FGC。 通過jcmd GC.run手動gc后,再次dump發現om.mysql.jdbc.NonRegisteringDriver保存的連接數目減少了

  關於問題(2):  通過排查,發現tomcat連接池設置的最大生命周期 max-age:60000, 即連接創建一分鍾后就會被銷毀。 

三、解決問題:

  1. 增加連接池超時時間,超時時間稍微小於mysql的waitTimeout即可

  2. jvm遲遲沒有fgc導致連接池中連接沒有釋放,可以稍微調小堆內存

  3. 回歸到最開始的疑問,為什么會是堆外內存溢出呢? 通過堆外內存幾個區域的分析,發現其中大量的MysqlStatement Cancellation Timer:

      mysql每個連接一個超時檢測線程用於檢測sql語句是否超時,mysql的連接沒有釋放導致線程也未釋放。 而線程棧占用默認大小為1m,所以導致了堆外內存溢出

 

 

 

 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM