不是,哥們,誰教你這樣處理生產問題的?
你好呀,我是歪歪。
最近遇到一個生產問題,我負責的一個服務觸發了內存使用率預警,收到預警的時候我去看了內存使用率已經到了 80%,看了一眼 GC 又發現還沒有觸發 FullGC,一次都沒有。
基于這個現象,當時推測有兩種可能,一種是內存溢出,一種是內存泄漏。
好,假設現在是面試,面試官目前就給了這點信息,他問你到底是溢出還是泄漏,你怎么回答?
在回答之前,我們得現明確啥是溢出,啥情況又是泄漏。
內存溢出(OutOfMemoryError):內存溢出指的是程序請求的內存超出了 JVM 當前允許的最大內存容量。當 JVM 試圖為一個對象分配內存時,如果當前可用的堆內存不足以滿足需求,就會拋出 java.lang.OutOfMemoryError 異常。這通常是因為堆空間太小或者由于某些原因導致堆空間被占滿。 內存泄漏 (Memory Leak):內存泄漏是指不再使用的內存空間沒有被釋放,導致這部分內存無法再次被使用。雖然內存泄漏不會立即導致程序崩潰,但它會逐漸消耗可用內存,最終可能導致內存溢出。
雖然都與內存相關,但它們發生的時機和影響有所不同。內存溢出通常發生在程序運行時,當數據結構的大小超過預設限制時,常見的情況是你要分配一個大對象,比如一次從數據中查到了過多的數據。
而內存泄漏和“過多”關系不大,是一個細水長流的過程,一次內存泄漏的影響可能微乎其微,但隨著時間推移,多次內存泄漏累積起來,最終可能導致內存溢出。
概念就是這個概念,這兩個玩意經常被大家搞混,所以多嘴提一下。
概念明確了,回到最開始這個問題,你怎么回答?
你回答不了。
因為這些信息太不完整了,所以你回答不了。
面試的時候面試官就喜歡出這種全是錯誤選項的題目來迷惑你,摸摸你的底子到底怎么樣。
首先,為什么不能判斷,是因為前面說了:一次 FullGC 都沒有。
雖然現在內存使用率已經到 80% 了,萬一一次 FullGC 之后,內存使用率又下去了呢,說明程序沒有任何問題。
如果沒有下去,說明大概率是內存溢出了,需要去代碼里面找哪里分配了大對象了。
那如果下去了,能說明一定沒有內存泄漏嗎?
也不能,因為前面又說了:內存泄漏是一個細水長流的過程。
關于內存溢出,如果監控手段齊全到位的話,你就記住左邊這個走勢圖:

一個緩慢的持續上升的內存趨勢圖, 最后瘋狂觸發 GC,但是并沒有內存被回收,最后程序直接崩掉。
內存泄漏,一眼定真假。
這個圖來自我去年寫的這篇文章:《雖然是我遇到的一個棘手的生產問題,但是我寫出來之后,就是你的了?!?/a>
里面就是描述了一個內存泄漏的問題,通過分析 Dump 文件的方式,最終成功定位到泄漏點,修復代碼。
一個不論多么復雜的內存泄漏問題,處理起來都是有方法論的。
不過就是 Dump 文件分析、工具的使用以及足夠的耐心和些許的運氣罷了。
所以我不打算贅述這些東西了,我想要分享的是我這次是怎么對應文章開始說的內存預警的。
我的處理方式就是:重啟服務。
是的,常規來說都是會保留現場,然后重啟服務。但是我的處理方式是:直接執行重啟服務的預案。沒有后續動作了。
我當時腦子里面的考慮大概是這樣的。
首先,這個服務是一個邊緣服務,它所承載的數據量不多,其業務已經超過一年多沒有新增,存量數據正在慢慢的消亡。代碼近一兩年沒啥改動,只有一些升級 jar 包,日志埋點這類的橫向改造。
其次,我看了一下這個服務已經有超過四個月沒有重啟過了,這期間沒有任何突發流量,每天處理的數據呈遞減趨勢,內存走勢確實是一個緩慢上升的過程,我初步懷疑是有內存泄漏。
然后,這個服務是我從別的團隊那邊接手的一個服務,基于前一點,業務正在消亡這個因素,我也只是知道大概的功能,并不知道內部的細節,所以由于對系統的熟悉度不夠,如果要定位問題,會較為困難。
最后,基于公司制度,雖然我知道應該怎么去排查問題,命令和工具我都會使用,但是我作為開發人員是沒有權限使用運維人員的各類排查工具和排查命令的,所以如果要定位問題,我必須請求協調一個運維同事幫忙。
于是,在心里默默的盤算了一下投入產出比,我決定直接重啟服務,不去定位問題。
按照目前的頻率,程序正常運行四五個月后可能會觸發內存預警,那么大不了就每隔三個月重啟一次服務嘛,重啟一次只需要 30s。一年按照重啟 4 次算,也就是才 2 分鐘。
這個業務我們就算它要五年后才徹底消亡,那么也就才 10 分鐘而已。
如果我要去定位到底是不是內存泄露,到底在哪兒泄露的,結合我對于系統的熟悉程度和公司必須有的流程,這一波時間消耗,少說點,加起來得三五個工作日吧。
10 分鐘和三五個工作日,這投入產出比,該選哪個,一目了然了吧?
我分享這個事情的目的,其實就是想說明我在這個事情上領悟到的一個點:在工作中,你遇到的問題,不是每一個都必須被解決的,也可以選擇繞過問題,只要最終結果是好的就行。
如果我們拋開其他因素,只是從程序員的本職工作來看,那么遇到諸如內存泄漏的問題的時候,就是應該去定位問題、解決問題。
但是在職場中,其實還需要結合實際情況,進行分析。
什么是實際情況呢?
我前面列出來的那個“首先,其次,然后,最后”,就是我這個問題在技術之外的實際情況。
這些實際情況,讓我決定不用去定位這個問題。
這也不是逃避問題,這是權衡利弊之后的最佳選擇。
同樣是一天的時間,我可以去定位這個“重啟就能解決”的問題,也可以去做其他的更有價值事情,敲一些業務價值更大的代碼。
這個是需要去權衡的,一個重要的衡量標準就是前面說的:投入產出比。
關于“不是所有的問題都必須被解決的,也可以選擇繞過問題”這個事情,我再給你舉一個我遇到的真實的例子。
幾年前,我們團隊遇到一個問題,我們使用的 RPC 框架是 Dubbo,有幾個核心服務在投產期間滾動發布的時候,流量老是弄不干凈,導致服務已經下線了,上游系統還在調用。
當時安排我去調研一下解決方案。
其實這就是一個優雅下線的問題,但是當時資歷尚淺,我認真研究了一段時間,確實沒研究出問題的根本解決方案。
后來我們給出的解決方案就是做一個容錯機制,如果投產期間有因為流量不干凈的問題導致請求處理失敗的,我們把這些數據記錄下來,然后等到投產完成后再進行重發。
沒有解決根本問題,選擇繞過了問題,但是從最終結果上看,問題是被解決了。
再后來,我們搭建了雙中心。投產之前,A,B 中心都有流量,每次投產的時候,先把所有流量從 A 中心切到 B 中心去,在 A 中心沒有任何流量的情況下,進行服務投產。B 中心反之。
這樣,從投產流程上就規避了“流量老是弄不干凈”的問題,因為投產的時候對應的服務已經沒有在途流量了,不需要考慮優雅的問題了,從而規避了優雅下線的問題。
問題還是沒有被解決,但是問題被徹底繞過。
最后,再舉一個我在知乎上看到的一個回答,和我想要表達的觀點,有異曲同工之妙:
https://www.zhihu.com/question/634940930/answer/3336285780

這個回答下面的評論也很有意思,有興趣的可以去翻一下,我截取兩個我覺得有意思的:



在職場上,甚至在生活中,一個雖然沒有解決方案但是可以被繞過的問題,我認為不是問題。
但是這個也得分情況,不是所有問題都能繞開的,假如是一個關鍵服務,那肯定不能置之不理,硬著頭皮也得上。
關鍵是,我在職場上和生活中遇到過好多人,遇到問題的時候,似乎只會硬著頭皮往上沖。
只會硬著頭皮往上沖和知道什么時候應該硬著頭皮往上沖,是兩種截然不同的職場階段。
所以有時候,遇到問題的時候,不要硬上,也讓頭皮休息一下,看看能不能繞過去。

浙公網安備 33010602011771號