Java 記憶體溢位排查
Java OOM 毫無疑問是開發人員常見並且及其痛恨的問題,但是任何服務的開發都沒法避免 OOM。 因此,OOM 的排查及定位是每個 Java 工程師都必備的技能。
所遇到的問題
在使用 scala 開發的一個 web 服務,在使用者使用中,經常出現: java.lang.OutOfMemoryError: Java heap space
。而且還束手無策,每次都只能重啟服務解決。
準備
服務使用 jetty 釋出的,先來看一下我這個服務的啟動引數:
/opt/soft/jdk/jdk1.7.0_40/bin/java \ -server -Xmx4G -XX:MaxPermSize=1024M -XX:PermSize=256M \ -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:-CMSConcurrentMTEnabled -XX:CMSInitiatingOccupancyFraction=65 -XX:+CMSParallelRemarkEnabled \ -XX:+HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPath=/opt/soft/heapdump/ \ -Dscala.concurrent.context.numThreads=500 \ -Dscala.concurrent.context.maxThreads=500 \ -Dfile.encoding=UTF-8 -jar start.jar >> log 2>&1 &
排查
通過增加了引數 -XX:+HeapDumpOnOutOfMemoryError
和 -XX:HeapDumpPath
當在 OOM 的時候,服務會生成一個 java_pid$pid.hprof
二進位制檔案。
下面就是使用工具分析這個 .hprof
檔案來定位問題了。使用 Memory Analyzer (MAT) 來分析該檔案,效果如下:
效果很嚇人,什麼鬼,什麼東西,吃了 3.8G 的記憶體,我#%$#@#@#&^&^&#$…. 開啟 Leak Suspects» Leaks» Problem Suspect 1 看到如下詳情:
一開始可能沒那麼快找到問題,但是這個圖已經很明顯說明了問題,是 ArrayList
ArrayList
在左側欄看 Attribute
。
然後一直滑鼠右鍵 into
進去看裡面的詳情,最終是可以看內容的。
問題原因
問題排查到最後,看到的是 ArrayList
裡面存的全是 ResponseBodyPart
, 然後就想到了專案使用到 Dispatch 請求下載結果檔案,
於是乎去找到問題程式碼,錯誤程式碼如下:
val outputReq = dispatch.url(url) / "task" / "output" / id val outputFuture = Http(outputReq > { res => val out = new FileOutputStream(outputFile(taskId), true) IOUtils.copy(res.getResponseBodyAsStream(), out) out.close })
看不出問題,感覺一切正常。翻原始碼會發現,res.getResponseBodyAsStream()
之前,已經將所有內容都存入一個 ArrayList
當中了。哎,沒用對啊。
解決辦法
問題已經定位到,於是去了解了一下這個專案,該如何使用 stream 的方式來讀取並寫入檔案流。然後發現,人家有一個 read line by line 的實現。但是切割上其實是有問題的,因為拿到一批 bytes 之後,直接轉成了 string 並用分隔符分割, 奈何內容裡面有中文,出現亂碼了。
最終,參考專案本身的 as.stream.Lines
寫了一個 as.stream.Bytes
來通過 bytes 邊讀邊寫,如下:
val bos = new BufferedOutputStream(new FileOutputStream("/tmp/file.txt", true)) val outputFuture = Http(outputReq > as.stream.Bytes(bytes => { bos.write(bytes) }))
總結
主要描述了分析問題的思路和方向,問題都大同小異,OOM 總會有原因的,有原因肯定可以找到並解決。MAT 這個分析工具很實用,內容很詳細。以前遇到 OOM 問題都是重啟服務,治標不治本,還是要多分析問題並解決。