前言
自己個人興趣愛好,線上有一個阿里雲伺服器,處理資料用的,會頻繁IO和分析資料。隔一段時間就會卡死(大概2個月),重啟就OK。本來沒當一回事,直到後來影響到賺取money了才引起重視。服務的啟動指令碼如下:
nohup java -Xms512m -Xmx1024m -jar xxx.jar &
當然這個指令碼是有很多問題的,畢竟自己的伺服器,追求的就是一個簡單粗暴,怎麼簡單怎麼來,沒有那麼多顧慮。但是自己埋的坑、遲早有一天哭著也要填完!
現象
突然有一天,登入伺服器發現登入不上去,xshell 類似下面的提示。看起來是連線上了,但是進不去,這種情況一般要麼是網路卡、要麼是伺服器卡,我這顯然是後者。透過阿里雲控制檯都進不去,一般這種情況等一段時間,消耗記憶體的應用會被殺掉,然後就能進去了。當然我這種急性子自然是選擇去阿里雲後臺直接強制重啟伺服器。
伺服器是能進去了,但是沒有任何日誌可供查詢,沒有堆疊資訊,檢視日誌並沒有發現oom錯誤(這裡只能一句臥槽了)。於是修改啟動指令碼如下,以便下一次有類似情況可以有痕跡可尋:
nohup java -Xms512m -Xmx1024m -Xlog:gc*:file=gc.log:time,uptime,level,tags -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dumps -jar xx.jar &
記憶體的原因,自然是要關注記憶體。一般我們需要關注一下垃圾回收器的行為以及堆記憶體使用情況。-XX:+HeapDumpOnOutOfMemoryError 以及-XX:HeapDumpPath 就是指定了當發生堆記憶體溢位的時候,轉儲當時的記憶體檔案供我們後續分析(其實觀察到記憶體開銷持續上漲,回收效果不明顯,就可以用這個命令導來匯出堆記憶體檔案分析:jamp -dump:live,format=b,file=xxx.hprof)。終於是在某一天(2個月後),發現了OOM錯誤,如下所示。
這就很離譜了,上次我卡死直接強制重啟了,nohup.out檔案並沒有看到OOM錯誤的日誌,但是這次為啥出現OOM錯誤日誌了。這個問題現在我都沒搞清楚為什麼,伺服器是2G記憶體,啟動指令碼堆最大1G記憶體,完全夠用哇,講道理不可能消耗完伺服器記憶體。難道程式碼還有IO等記憶體洩漏? 先不管了,先解決眼前的問題。既然是OOM錯誤,自然是要看是哪個例項佔用了記憶體,使用MAT分析一下。(PS: JHAT 無異於大海撈針,因為類太多,他又不能把較大記憶體的排序,然後OQL又不能支援萬用字元,就無解,還是MAT香)。MAT分析報告如下:
可以看到有一個大物件佔用記憶體692.4M , 這基本可以鎖定這個問題就出現在這個物件上了。 然後再查其中有一個屬性,是一個靜態成員變數map,如圖所示有6291456個key。 結合程式碼看該變數會被業務資料依據具體情況,填充進資料,但是並沒有釋放的地方,so 這就是問題所在了。
解決
我這裡就是簡單粗暴的定時清理一下記憶體,如下所示。
因為要很久才出現OOM, 所以這個程式碼能用,但卻是不優雅的。奈何自己的專案隨便弄的,寫成了屎山程式碼,不太好改,畢竟有一句話說的好: If your code runs in some inexplicable way, don't touch it anymore。否則將會是面對如下所示的殘酷:
後話
文中有提到設定了-Xmx1024m 但是實際導致伺服器2G記憶體卡死,可能存在的記憶體洩漏問題,先在啟動指令碼加上引數-XX:MaxDirectMemorySize=256M,控制一下本地記憶體,後續如果再次出現OOM 了,在繼續-。
nohup java -Xms512m -Xmx1024m -Xlog:gc*:file=gc.log:time,uptime,level,tags -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dumps -XX:MaxDirectMemorySize=256M -jar xx.jar &
大家不偷懶的話,gc日誌引數,堆記憶體匯出,這些最好都設定好!