一次OOM問(wèn)題排查過(guò)程實(shí)戰(zhàn)記錄
上周運(yùn)維反饋線上程序出現(xiàn)了OOM,程序日志中的輸出為
Exception in thread "http-nio-8080-exec-1027" java.lang.OutOfMemoryError: Java heap space Exception in thread "http-nio-8080-exec-1031" java.lang.OutOfMemoryError: Java heap space
看線程名稱應(yīng)該是tomcat的nio工作線程,線程在處理程序的時(shí)候因?yàn)闊o(wú)法在堆中分配更多內(nèi)存出現(xiàn)了OOM,幸好JVM啟動(dòng)參數(shù)配置了-XX:+HeapDumpOnOutOfMemoryError,使用MAT打開(kāi)拿到的hprof文件進(jìn)行分析。
第一步就是打開(kāi)Histogram看看占用內(nèi)存最大的是什么對(duì)象:

可以看到byte數(shù)組占用了接近JVM配置的最大堆的大小也就是8GB,顯然這是OOM的原因。
第二步看一下究竟是哪些byte數(shù)組,數(shù)組是啥內(nèi)容:

可以看到很明顯這和HTTP請(qǐng)求相關(guān),一個(gè)數(shù)組大概是10M的大小。
第三步通過(guò)查看GC根查看誰(shuí)持有了數(shù)組的引用:

這符合之前的猜測(cè),是tomcat的線程在處理過(guò)程中分配了10M的buffer在堆上。至此,馬上可以想到一定是什么參數(shù)設(shè)置的不合理導(dǎo)致了這種情況,一般而言tomcat不可能為每一個(gè)請(qǐng)求分配如此大的buffer。
第四步就是檢查代碼里是否有tomcat或服務(wù)器相關(guān)配置,看到有這么一個(gè)配置:
max-http-header-size: 10000000
至此,基本已經(jīng)確定了八九不離十就是這個(gè)不合理的最大http請(qǐng)求頭參數(shù)導(dǎo)致的問(wèn)題。
到這里還有3個(gè)疑問(wèn):
- 即使一個(gè)請(qǐng)求分配10M內(nèi)存,堆有8GB,難道當(dāng)時(shí)有這么多并發(fā)嗎?800個(gè)tomcat線程?
- 參數(shù)只是設(shè)置了最大請(qǐng)求頭10M,為什么tomcat就會(huì)一次性分配這么大的buffer呢?
- 為什么會(huì)有如此多的tomcat線程?感覺(jué)程序沒(méi)這么多并發(fā)。
先來(lái)看問(wèn)題1,這個(gè)可以通過(guò)MAT在dump中繼續(xù)尋找答案。
可以打開(kāi)線程視圖,搜索一下tomcat的工作線程,發(fā)現(xiàn)線程數(shù)量的確很多有401個(gè),但是也只是800的一半:

再回到那些大數(shù)組的清單,按照堆分配大小排序,往下看:

可以發(fā)現(xiàn)除了有10008192字節(jié)的數(shù)組還有10000000字節(jié)的數(shù)組,查看引用路徑可以看到這個(gè)正好是10M的數(shù)組是output buffer,區(qū)別于之前看到的input buffer:

好吧,這就對(duì)了,一個(gè)線程分配了輸入輸出兩個(gè)buffer,占用20M內(nèi)存,一共401個(gè)線程,占用8GB,所以O(shè)OM了。
還引申出一個(gè)問(wèn)題為啥有這么多工作線程,
再來(lái)看看問(wèn)題2,這就需要來(lái)找一下源碼了,首先max-http-header-size是springboot定義的參數(shù),查看springboot代碼可以看到這個(gè)參數(shù)對(duì)于tomcat設(shè)置的是MaxHttpHeaderSize:

然后來(lái)看看tomcat源碼:

進(jìn)一步看一下input buffer:

buffer大小是MaxHttpHeaderSize+ReadBuffer大小,這個(gè)默認(rèn)是8192字節(jié):
<attribute name="socket.appReadBufSize" required="false"> <p>(int)Each connection that is opened up in Tomcat get associated with a read ByteBuffer. This attribute controls the size of this buffer. By default this read buffer is sized at <code>8192</code> bytes. For lower concurrency, you can increase this to buffer more data. For an extreme amount of keep alive connections, decrease this number or increase your heap size.</p> </attribute>
這也就是為什么之前看到大量的buffer是10008192字節(jié)的。至于為什么分配的buffer需要是MaxHttpHeaderSize+ReadBuffer。顯然還有一批內(nèi)容是空的10000000字節(jié)的buffer應(yīng)該是output buffer,源碼可以印證這點(diǎn):

嗯這是一個(gè)header buffer,所以正好是10000000字節(jié)。
至于問(wèn)題3,顯然我們的應(yīng)用程序是配置過(guò)最大線程的(查看配置后發(fā)現(xiàn)的確,我們配置為了2000,好吧有點(diǎn)大),否則也不會(huì)有401個(gè)工作線程(默認(rèn)150),如果當(dāng)時(shí)并發(fā)并不大的話就一種可能,請(qǐng)求很慢,雖然并發(fā)不大,但是因?yàn)檎?qǐng)求執(zhí)行的慢就需要更多線程,比如TPS是100,但是平均RT是4s的話,就是400線程了。這個(gè)問(wèn)題的答案還是可以通過(guò)MAT去找,隨便看幾個(gè)線程可以發(fā)現(xiàn)很多線程都在等待一個(gè)外部服務(wù)的返回,這說(shuō)明外部服務(wù)比較慢,去搜索當(dāng)時(shí)的程序日志可以發(fā)現(xiàn)有很多"feign.RetryableException: Read timed out executing的日志"。。。。追殺下游去!慢點(diǎn),我們的feign的timeout也需要再去設(shè)置一下,別被外部服務(wù)拖死了。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
使用科大訊飛語(yǔ)音SDK實(shí)現(xiàn)文字在線合成語(yǔ)音
這篇文章主要介紹了使用科大訊飛語(yǔ)音SDK實(shí)現(xiàn)文字在線合成語(yǔ)音 的相關(guān)資料,需要的朋友可以參考下2015-12-12
Android實(shí)現(xiàn)懸浮對(duì)話框代碼
這篇文章主要介紹了Android實(shí)現(xiàn)懸浮對(duì)話框代碼的相關(guān)資料,需要的朋友可以參考下2016-03-03
Android獲取ROOT權(quán)限的實(shí)例代碼
這篇文章主要介紹了Android如何獲取ROOT權(quán)限,寫了一個(gè)小方法,大家可以在應(yīng)用中檢測(cè)ROOT權(quán)限2013-11-11
Android實(shí)現(xiàn)應(yīng)用內(nèi)置語(yǔ)言切換功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)應(yīng)用內(nèi)置語(yǔ)言切換功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-02-02
Android實(shí)現(xiàn) EditText輸入手機(jī)號(hào)空格功能
這篇文章主要介紹了Android實(shí)現(xiàn) EditText輸入手機(jī)號(hào)空格功能,實(shí)現(xiàn)思路是要重寫TextWatcher,每次EditText內(nèi)容變化,都判斷內(nèi)容是否符合要求,具體實(shí)例代碼大家參考下本文2018-02-02
Flutter學(xué)習(xí)之實(shí)現(xiàn)自定義themes詳解
一般情況下我們?cè)趂lutter中搭建的app基本上都是用的是MaterialApp這種設(shè)計(jì)模式,MaterialApp中為我們接下來(lái)使用的按鈕,菜單等提供了統(tǒng)一的樣式,那么這種樣式能不能進(jìn)行修改或者自定義呢?答案是肯定的,一起來(lái)看看吧2023-03-03
Android RecyclerView打造懸浮效果的實(shí)現(xiàn)代碼
本篇文章主要介紹了Android RecyclerView打造懸浮效果的實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10
Android應(yīng)用程序窗口(Activity)窗口對(duì)象(Window)創(chuàng)建指南
本文將詳細(xì)介紹Android應(yīng)用程序窗口(Activity)的窗口對(duì)象(Window)的創(chuàng)建過(guò)程,需要了解的朋友可以參考下2012-12-12

