1. 程式人生 > >記一次尷尬的Java應用記憶體洩露排查

記一次尷尬的Java應用記憶體洩露排查

這星期被線上JVM記憶體佔用不斷增大的問題所困擾,自己提出了一些假設,然後去實施驗證都一一失敗了,有一些經驗和教訓在這裡分享下. 之所以是尷尬,是最後因為偶爾出現修復了另一個問題導致記憶體不再上升,但這之間的關係還未明瞭,還需要繼續追蹤. 這裡講述一下這次排查的過程.

直接記憶體的錯誤判斷

伺服器的JVM配置為Xmx3g,使用G1,沒有設定Xms考慮自然收縮和fgc之後的空間回攏. 使用NMT檢視committed記憶體不足4G,但實際情況記憶體佔用不斷增大,甚至出現了OOM killer殺掉java程序.

最開始考慮是直接記憶體的問題,觀察GC行為發現沒有發生過一次fgc,想當然覺得應該是直接記憶體無法回收,並且在手動執行gc之後記憶體佔用的確是下降的. 於是調整了一波JVM引數,將堆大小設定為2500M,並配置MaxDirectMemory用來觸發fgc. 但最後發現記憶體依舊增長,且還是沒有fgc. 再觀察了下heap(jmap -heap)的情況,發現手工觸發fgc後,heap釋放的大小和記憶體下降幾乎一致. 在使用JMX查看了direct pool(java.nio.BufferPool)後驚奇地發現..直接記憶體並沒有多少使用(我們底層用了netty,下意識認為出了問題):

圖中所示最大隻用了4M左右.

最後設定了Xms之後發現fgc已經沒有作用,記憶體的使用量不會再下降,排除掉了這個因素. 這個錯誤的判斷最主要的原因還是先入為主,沒有收集支撐的直接證據.

stack中的間隙

接下去只能使用pmap(pamp -X pid)看下具體的記憶體資訊.

發現了一些有趣的東西,大量的64M塊. 直覺告訴我,快找出問題了. 搜尋了下發現類似的問題:

解決方法也很直接換一個分配記憶體的庫. 可選的也只有tcmalloc和jemalloc.

選擇了前者,配置之後效果出眾. 已經沒有上圖中那麼多的64M塊了. 並且啟動後的記憶體使用是掉了下來,但是好景不長.

(藍線是使用了tcmalloc,紅線是對比方) 剛開始的確藍線記憶體使用好一點(紅線在20:00的時候重啟 記憶體降低了). 但隨後還是增長了,比較迷的一點是在8點後記憶體陡降(但JVM沒有重啟,PID也沒有變化),恢復到了最初的水平.

看來問題不在這裡.

看似沒有關聯的問題

周常部署的時候出現一個問題,導致heap OOM,我跟了下發現了一段程式碼記憶體溢位,但看似和這個堆外記憶體引起的問題沒有關聯就沒怎麼在意.

第二天同事修復了這個問題,遷移走了這個介面到另一個服務. tcmalloc的heap profiling無法使用,一開啟libunwindsegment fault退出,換成jemalloc後開啟. 還在想是不是常見的那個zip溢位的問題,但是搜尋了程式碼庫沒有發現忘記關閉的情況,等著看下增長和dump驗證下. 觀察了一會兒,突然發現記憶體不再增長了.

(藍線是之前一直留著的機器,中間降低是進行了重啟) 非常平穩地度過了一天.

那坨profiling也失去了意義... ...

這就感覺非常尷尬了...

繼續追蹤

更尷尬的是另一個服務炸了... ... 繼續跟進,先把jemalloc配置過去,等下一次增長看下dump.

隨著時間的推移遇見了各種各樣的問題. 這次排查除了對之前積累的知識的運用,更多像是一次對搜尋引擎熟練度的提升 :( .

但過程還是很不錯的,也看到了一些自己以前沒有接觸過的知識. 深深感受到自己知識的匱乏,就像深淵(??)一樣,逐漸深入才會發現不一樣的風景. 生活中還是要多(xie)些bug才會更精彩的啊... ...

參考資料: