現象:
工作時遇到某個服務老是頻繁重啓,日誌報錯爲OOM
分析:
- 出現OOM是因爲整個堆內存不夠用了,此時JVM首先嚐試擴展更多的空間,其次GC嘗試回收內存,前兩種方法無果的情況下只能報OOM並退出
- 可能的情況:內存不夠、內存泄漏
嘗試解決問題的步驟:
- 加上JVM參數 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath= ,設置當出現OOM時,dump整個堆的信息
- 等OOM後,將文件拷貝到電腦上
- 用JDK自帶的 visualVM,打開dump文件
- 設置了最大堆大小 512M,從下圖看出,確實佔滿了導致OOM
- 轉到類實例佔用大小視圖,找到佔用最大的類
可以看到,總共512M堆大小,byte[]對象佔用了其中的90%,這顯然是異常佔用
接下來轉到實例視圖,查看具體的實例
最大的byte[]對象佔用了 約 10M
拷貝byte[]對象中存儲的內容,並在代碼中構建byte[]對象存入String打印出可視化內容
打印出的部分String內容如下,可以看到存儲的是 http header的內容,並且byte[]中99%的內容爲0,說明大量空間並未被使用到
HTTP/1.1 200
Access-Control-Allow-Origin: *
Access-Contr
選其中的一個,選擇顯示最近的垃圾回收根節點
看到持有這個byte[]對象的是一個 HeapByteBuffer對象,HeapByteBuffer是java NIO中的對象。
程序中沒有使用NIO,推測NIO應該在Tomcat中被使用,並且Tomcat的默認配置不可能爲 10M這麼不合理的值,那感覺可能是有不合理的自定義配置存在。
於是先去項目中找到如下相關配置:
發現,Tomcat中最大請求頭大小被設置爲 10M,和剛纔byte[]對象佔用的大小相似(多出的應爲對象頭以及其他多申請的空間,具體要參考源碼),其次也和前面發現的byte[]對象中存儲的是請求頭信息的事實相符合,這應該就是問題所在,把這個配置調小點或者乾脆使用默認配置即可。
問題總結:
諮詢了相關同事,爲了傳輸較大的文件,調大了 tomcat max-http-post-size,順手改了 max-http-header-size,容器初始化處理請求的線程池時,每個線程都會申請 此處爲 10M大小的byte[]對象,並且請求處理線程的生命週期一般和服務的生命週期一致,也就是說,線程持有的 byte[]對象在整個服務週期中是一直存活的。一般線程池的規模少說也在幾十個,也意味着服務正常工作時,幾百兆的堆內存(也可能是堆外內存,具體看Tomcat配置使用哪個)會被請求處理線程一直佔用,當分配的內存較少時,很快OOM