某天凌晨,手機忽然告警,線上某臺機子內存使用率超過90%,當時覺得是有定時任務在跑,再加上夜已深了,沒有去排查具體緣由。次日早上有發佈,內存降下來了,白天就沒有去調查這個問題。等到傍晚高峯期的時候,又接到內存調用超過90%的告警,而且持續一段時間後,線上的某臺機子掛了。
當時一臺機子掛掉後,立刻重啓了掛掉的機子,而且把另外一臺機子的內存信息dump下來。
java
jmap -dump:format=b,file=文件名 [pid]
服務器
在dump過程當中遇到小插曲,沒法dump下來。jvm
這種狀況是由於非當前線程用戶致使,在命令前面加上sudo -u 用戶
便可
工具
同時保存了線程信息 測試
sudo -u jetty jstack pid > /tmp/jstack2018-04-18.txt
字體
查看服務器系統日誌
cat /var/log/messages
spa
上圖看到 Killed process 7364, UID 502, (java) total-vm:10511252kB, anon-rss:7489308kB
java內存使用了7G多。ps:上一行的java進程的2627813 和 1872416都是頁數,每頁4K。.net
能夠發現容器內存使用由於超過系統內存被kill掉了。線程
使用mat分析具體內存泄漏問題。
因爲本人對mat工具使用的還不熟練,看到分佈圖後,total一共才103.6MB,覺得堆內內存沒有問題,便往堆外內存溢出方向去考慮。陸陸續續的檢查了代碼中的ThreadLocal的使用,對象也有及時清理,檢查了線上線程的數量,都沒有發現明顯問題。3d
轉載一篇線程過多致使的堆外內存溢出文章:線程數過多致使堆外內存溢出
繼續使用mat分析,使用Leak Suspects功能,發現內存中有大量的TrueTypeFont對象
正好想到在該應用中,有使用到字體畫圖,發送圖片的功能。因而乎查詢一下OOM當時請求狀況,發現確實有大量的畫圖的請求,而且發現某個請求中同時畫600多張畫。
問題重現
接着,在公司的測試環境下模擬了下請求,果真內存使用率由60多一會兒升到了80多。
回頭檢查了一下畫圖的代碼,發如今處理字體的地方確實有問題。
該方法的邏輯爲加載服務器上的某個字體文件,使用畫筆畫一張二維碼,而後保存到服務器的臨時目錄下。
這裏在加載字體的時候沒作好處理,致使每次過來一個畫的任務就會加載一次字體文件,內存中建立一個字體對象,而咱們服務器上的字體文件大小約有16M,也就至關於每次畫一幅畫就須要加載16M的內存大小。
因爲當時對mat不熟悉,還在懷疑是否是Font操做了堆外空間致使的。因而乎準備測試一下堆外空間。
排除堆外空間溢出可能性
咱們服務器的配置爲8G內存,jvm配置爲 -Xmx4428m -Xms4428m -Xmn2767m,
堆外空間若是不限制的話,會和jvm使用差很少。這時候,咱們限定堆外空間大小,好比咱們指定堆外空間好比說只有100M -XX:MaxDirectMemorySize=100m
。
若是說Font使用的是堆外空間,那麼堆外空間就會很快到達100M,而且進行Full GC,阻塞全部請求,Stop The World。這時候,只要Full GC清理及時,後面阻塞請求繼續進來,繼續滿100M,繼續Full GC。這樣,內存使用率永遠也不會到8G,就不會出現被系統kill掉的狀況了。
可是在測試過程當中,發現內存使用率仍是在一路飆升,最終仍是被kill了。因此後來只能再次懷疑是堆內內存溢出。用一樣的策略,將堆內內存設置爲1G(100M應用起不來了),發現內存沒有繼續往上升,而且查看了下gc日誌,一直在進行Fulll GC,從這大體能夠看出使用的是堆內的內存。
由此能夠看出,當時高峯期,大量畫圖請求進來,致使大量的大對象加載進內存,最終致使jvm內存超過了系統內存,容器被系統kill掉。
將Font改成單例模式,內存中只存在一份。
在公司測試環境再次試了下,內存使用率沒有變化。發佈上線後,線上該問題沒有再復現,問題暫時告一段落。
後續調研發現,當請求量小的時候,內存上去後沒有OOM,可是內存一直沒有往降低,懷疑出現了內存泄漏。使用mat的Histogram搜索TrueTypeFont
再查看他的根回收節點。
過濾掉弱引用等對回收沒有影響的引用。
發現主要有這麼幾個類,咱們看最多的類Disposer。上網查了下Disposer的做用。
在mat中,我已通過濾掉了弱引用,剩下的發現有個FontStrikeDisposer對TrueTypeFont爲強引用。可是這些數量比較少,只有8個,應該起不到做用纔對。到此,頭緒就斷了,回頭擼擼TrueTypeFont的源碼看能不能有什麼發現。