1. 程式人生 > >Java 記憶體溢位排查

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 問題都是重啟服務,治標不治本,還是要多分析問題並解決。