那天伺服器內存被 Java 線程撐爆了,簡單的 JVM 問題診斷流程

java識堂 發佈 2020-04-25T01:59:27+00:00

話說那天中午吃飯的時候,一個同事說:「那個項目組的人快氣死我了,程序有問題,早晨在群里@了他們,到中午才回消息,然後竟然還說他們的程序沒有問題,是我們這邊調用的太頻繁了。簡直想笑。」

話說那天中午吃飯的時候,一個同事說:「那個項目組的人快氣死我了,程序有問題,早晨在群里@了他們,到中午才回消息,然後竟然還說他們的程序沒有問題,是我們這邊調用的太頻繁了。簡直想笑。」

一般來說,對接出現問題,如果不是錯誤太明顯,我首先都會先懷疑是不是自己出了問題,以免到時候丟人。所以我說,吃完飯回去我幫你排查一下,看問題到底出在哪裡。

背景說明

我們當前這個系統和很多的第三方系統做了集成,出問題的就是其中一個三方系統。其實很簡單,他們的系統會產生一些個人待辦任務,然後待辦任務的個數需要推送到我們的 APP 上,作為圖標的角標顯示。

用戶數據已經打通,其實很簡單的需求,角標通知也不要求實時,10分鐘刷一次就可以。這個場景非常典型,用消息隊列再合適不過了。他們把數據推到消息隊列,我們去消息隊列取,完美。

可現實並不是這樣,他們說系統是產品化,不支持消息隊列,只能把待辦任務接口開放出來。好的(微笑臉),你們是產品你們有理。可能有待辦任務的用戶不多,300多個,那就每隔 10 分鐘請求 300 多次請求唄。也沒用多線程,就是簡單的循環 300 多次請求,每次耗時差不多的1分鐘。

也可以,那就這樣唄。

順便說一下,這個服務的 JDK 是 1.6 版本,據說由於歷史原因,現在已經不敢升級了。而且,服務要部署在 windows 上。(你說神奇不神奇)

花明柳暗

那就這樣唄,做個定時任務,10分鐘咔咔請求個 300 來次,也挺過癮,也挺省心。

但是好景不長,天不遂人願,伺服器不遂程式設計師願。

以下是同事的經歷,我轉述以下。

就在定時任務跑起來後的第二個晚上,那本來該是一個平常的晚上,可是告警郵件擾人清夢。一看日誌,內存使用空間過高,撐爆了,導致機器自動重啟了。windows 就這點好啊,還會自動重啟(尷尬臉)。然後手動上去把服務啟動起來,解決。

隔了一天,還是晚上,又報警了,伺服器又自動重啟了,又是內存使用空間過高。又手動上去把服務啟動了。

於是他反饋給這個服務的開發人員,結果得到的回覆是:「我們的服務沒有問題,肯定是你們的調用有問題,你們把定時任務停掉肯定就好了,所以是你們的問題」。

於是,他過來找我,跟我說明情況,問我可能會是什麼問題。

我:你確定定時服務是 10 分鐘一次,沒有出現死循環嗎?

同事:確定。

我:那他們的服務有使用 redis 之類的外部緩存嗎?

同事:不知道。

我:。。。 既然你確定你調用的沒問題,那肯定是他們程序出現問題把內存撐爆了呀,這有什麼好懷疑的,讓他們改吧。

同事:他們現在說自己沒問題啊。

挖出真兇

好吧,既然他們說沒問題,那我就來幫他把問題找出來吧。於是,遠程進了那台 windows 伺服器。

這時候已經把定時任務已經跑了兩天了,16G 的內存已經用掉 15G 多了,眼看隨時有可能崩潰,然後把定時任務停掉,內存使用量也並不會下來。

我開始懷疑是不是用了 redis 之類的外部緩存,結果進伺服器一查 redis 、memcached 之類的壓根兒就沒裝,所以排除外部緩存。(隨後使用 JVM 工具查看也證明了這一點

那既然不是外部緩存,那肯定出在 JVM 上了,要不然就是用了 JVM 緩存,要不然就是內存泄漏什麼的。於是我想用 jinfo -flags看一下 JVM 初始參數,JDK 6 竟然還不支持 -flags 。

然後我不知道是不是嘗試了 jmap -heap 還是就看了一眼 jmap -help以為不支持 jamp -heap,反正最後我是通過 jconsole來觀察的 JVM。一看 JVM 參數明顯就是默認沒特殊設置過,並且奇怪的是對內存一共採用了 700 多M。700M 和 15G 比,差哪兒去了,沒道理啊,問題沒出在堆上。

然後我嘗試執行 GC 操作,然而並沒有任何改善。直到這裡,我嚴重懷疑是出現了內存泄漏了。

於是我執行了 jmap -dump,把堆、線程信息 dump 下來,然後拉到本地分析。不看不知道,一看嚇一跳,線程多到令人窒息。

不得不說,有一點他們做的非常好,竟然貼心的給線程編了號,沒錯,就是有這麼多線程 10萬多個。於是我們算了一下假設 10分鐘請求 300 次,那就是 300 個線程,一小時就是 30 x 6=1800,一天24小時就是1800 x 24=43200,兩天多的時間 10萬多個線程那就正好對上了,好牛x的樣子。

一個線程默認占用空間大小 1M,10萬多個線程那就是 10個多G,加上堆內存占用和機器上其他服務的內存占用,內存飆到 15G 就對的上了。

誰的問題誰處理

有問題就找問題就這麼難嗎,不承認自己的程序有問題是怎麼想的呢。

好啊,你們自己不查,我幫你找到問題原因了,滿意了吧。

於是,同事理直氣壯的把上面那張截圖發給他們,但是沒有額外說一句話。

下午,微信群里對方發來消息,問題已修改,可以再試試。

然後,好多天過去了,問題沒有再出現。

規避問題

有的同學問了,系統能創建10萬多個線程嗎,有可能的。

這個問題產生的原因就是線程創建了但是沒有銷毀,估計是銷毀邏輯寫的有些問題吧。

拋開邏輯錯誤不說,使用線程的正確做法是使用線程池,以免帶來不必要的性能損耗和這種未加控制、未及時銷毀帶來的線程無止境創建的問題。

創作不易,小小的贊,大大的暖,快來溫暖我。不用客氣了,贊我!



作者:古時的風箏
連結:https://juejin.im/post/5ea0f2a2f265da47aa3f7b0f

關鍵字: