背景
核心服務、并發較高,查詢接接口最高幾萬qps 對停頓比較敏感
jvm
- par new + cms、 堆分配較大,老年代6g、old gc水位 3G左右
- ygc幾分鐘一次、full gc 十天一次
分庫分表,對應多個數據庫連接池對象
問題發現
stw告警,full gc stw超過500ms
查看jvm gc曲線圖,old space一直慢慢上升,有內存泄露的感覺,但是fullgc后能回收掉
gc日志發現 cms最后一個階段 final remark 時候耗時較久(需要加上參數 -XX:+PrintReferenceGC
才能打印具體什么類型的引用)
dump heap 發現有大量 finalizer ,內部都是 pgconnection 數據庫連接對象
如何解決
- 該服務使用的是pgsql,不會像mysql那樣在數據庫服務側關閉 8小時空閑的連接,所以不需要 maxLifetime 在此之前(7小時)提前去重建, 置為 maxLifetime = 0 關閉該機制
- 同時連接池 minimumIdle 根據qps計算下來不需要那么大,適當調小
根因分析
其實maxLifetime 機制本身沒什么問題
maxLifeTime = 7 小時,當它清理連接的時候,gc age肯定是超過15的老對象了,要進入老年代
假設連接多個數據庫 n, db連接池 minimumIdle = m,那么每隔 maxLifetime 就會有 T = m * n * maxLifeTime 個連接進入老年代, 案例里 m=8 , n=35, maxLifeTime=7小時,fullgc 大概10天一次
那么full gc 的時候大概有 8 * 35 * (10天 * 24 /maxLifeTime) = 9600個連接對象在老年代 非常多
連接的庫越多、連接池min個數設置越大、fullgc間隔時間越長(老年代空間越大),那么 cms final remark 暫停就越久; 如果數據庫選用的mysql,最好保留maxLifeTime機制,合理設置minimumIdle ,同時 jvm 堆大小不能一味的往大了設置,太大會導致 ygc、fullgc耗時變長