背景
在後端服務改爲在 Docker + Kubernetes 上部署後沒多久,程序就由於 java.lang.OutOfMemoryError: Java heap space 原因退出重新啓動。
問題分析
下面是日誌文件的最後輸出:
java.lang.OutOfMemoryError: Java heap space
Dumping heap to /app/tmp/java_pid16.hprof ...
Heap dump file created [384896308 bytes in 5.099 secs]
Exception in thread "http-nio-8080-Acceptor-0"
導致 OOM 的直接原因就是堆內存不夠了,要解決這個問題可以從兩個角度出發,一個是擴大 MaxHeapSize 的值,另一個是找出程序中存在的問題,比如沒有及時釋放不用的對象,或者使用了很多的大對象等。下面就分別從宏觀角度和微觀角度去分析解決方案。
宏觀角度分析
解決這個問題最簡單粗暴的方法就是給 MaxHeapSize 設置更大的值。首先我們先看下程序堆內存相關信息:
jmap -heap PID
在輸出的信息中看到最大堆內存爲 256M (MaxHeapSize = 268435456 (256.0MB))。可以明確的是程序 OOM 的最大原因是因爲堆的最大內存太小了,再去深究原因爲什麼 MaxHeapSize 值是 256M呢?在 JVM 的啓動參數中並沒有找到堆最大內存的設置,那麼 JVM 在啓動時就會根據可用內存計算出這個值,爲可用內存的 1/4。從運維那裏得知 Kubernetes 中的 單個 Pod 配置了最大可用內存爲 1G,這就剛好對應上了。
經過上面的分析,需要注意的是設置最大堆內存大小,因此設置 JVM 參數: -Xmx1024m
微觀角度分析
擴大堆內存最大值可以解決 OOM 問題,但是我們並沒有找到真正導致 OOM 問題的原因(當然在這個例子中的最大原因是 MaxHeapSize 值太小),這時就需要分析堆內存中的具體有哪些對象了。
我們可以通過 Eclipse 的 Memory Analyzer Tool (MAT) 工具來分析堆內存 dump文件,來查看具體哪些對象比較佔用內存。這裏需要注意的是要設置相關的參數,用於在程序 OOM 時自動保存堆Dump文件 :
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$APP_HOME/tmp
在程序運行中也可以通過下面命令生產堆dump 文件:
jmap -dump:format=b,file=rds.bin PID
通過 MAT 工具,我們可以找到那些對象比較佔用內存,在對應找到程序中相關的代碼,做出相應的修改。
總結
經過上述分析,一方面要注意的是根據實際情況設置堆內存最大值,如果不設置將會根據可用內存動態計算。另一個需要注意的是要設置 OOM 時自動保存堆dump 信息,這樣在出現事故時,才能夠內入分析是什麼原因導致的 OOM。