我們當前這個系統和很多的第三方系統做了集成,出問題的就是其中一個三方系統。其實很簡單,他們的系統會產生一些個人待辦任務,然后待辦任務的個數需要推送到我們的 APP 上,作為圖標的角標顯示。
用戶數據已經打通,其實很簡單的需求,角標通知也不要求實時,10分鍾刷一次就可以。這個場景非常典型,用消息隊列再合適不過了。他們把數據推到消息隊列,我們去消息隊列取,完美。
可現實並不是這樣,他們說系統是產品化,不支持消息隊列,只能把待辦任務接口開放出來。好的(微笑臉),你們是產品你們有理。可能有待辦任務的用戶不多,300多個,那就每隔 10 分鍾請求 300 多次請求唄。也沒用多線程,就是簡單的循環 300 多次請求,每次耗時差不多的1分鍾。
也可以,那就這樣唄。
順便說一下,這個服務的 JDK 是 1.6 版本,據說由於歷史原因,現在已經不敢升級了。而且,服務要部署在 windows 上。(你說神奇不神奇)
花明柳暗
那就這樣唄,做個定時任務,10分鍾咔咔請求個 300 來次,也挺過癮,也挺省心。

但是好景不長,天不遂人願,服務器不遂程序員願。
以下是同事的經歷,我轉述以下。
就在定時任務跑起來后的第二個晚上,那本來該是一個平常的晚上,可是告警郵件擾人清夢。一看日志,內存使用空間過高,撐爆了,導致機器自動重啟了。windows 就這點好啊,還會自動重啟(尷尬臉)。然后手動上去把服務啟動起來,解決。
隔了一天,還是晚上,又報警了,服務器又自動重啟了,又是內存使用空間過高。又手動上去把服務啟動了。
於是他反饋給這個服務的開發人員,結果得到的回復是:“我們的服務沒有問題,肯定是你們的調用有問題,你們把定時任務停掉肯定就好了,所以是你們的問題”。
於是,他過來找我,跟我說明情況,問我可能會是什么問題。
我:你確定定時服務是 10 分鍾一次,沒有出現死循環嗎?
同事:確定。
我:那他們的服務有使用 redis 之類的外部緩存嗎?
同事:不知道。
我:。。。 既然你確定你調用的沒問題,那肯定是他們程序出現問題把內存撐爆了呀,這有什么好懷疑的,讓他們改吧。
同事:他們現在說自己沒問題啊。
挖出真凶
好吧,既然他們說沒問題,那我就來幫他把問題找出來吧。於是,遠程進了那台 windows 服務器。
這時候已經把定時任務已經跑了兩天了,16G 的內存已經用掉 15G 多了,眼看隨時有可能崩潰,然后把定時任務停掉,內存使用量也並不會下來。
我開始懷疑是不是用了 redis 之類的外部緩存,結果進服務器一查 redis 、memcached 之類的壓根兒就沒裝,所以基本排除外部緩存。
並且登錄上去之后查看進程內存占用,確實就是一個 Java 進程占了這么多內存。
那既然不是外部緩存,那肯定出在 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 就對的上了。
誰的問題誰處理
有問題就找問題就這么難嗎,不承認自己的程序有問題是怎么想的呢。
好啊,你們自己不查,我幫你找到問題原因了,滿意了吧。
於是,同事理直氣壯的把上面那張截圖發給他們,但是沒有額外說一句話。
下午,微信群里對方發來消息,問題已修改,可以再試試。
然后,好多天過去了,問題沒有再出現。