現象:
工作時遇到某個服務老是頻繁重啟,日誌報錯為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-Contr123
選其中的一個,選擇顯示最近的垃圾回收根節點
看到持有這個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