【程序大俠傳】應用內(nèi)存緩步攀升珠洗,告警如影隨形

前序

在武俠編碼的江湖中溜歪,內(nèi)存泄漏猶如隱秘殺手若专,潛伏于應用程序的各個角落,悄無聲息地吞噬著系統(tǒng)資源蝴猪。若不及時發(fā)現(xiàn)和解決调衰,必將導致內(nèi)存枯竭,應用崩潰自阱。

背景:內(nèi)存泄漏的由來
內(nèi)存泄漏嚎莉,乃程序運行過程中,已不再使用的內(nèi)存塊未被及時回收沛豌,導致內(nèi)存使用量不斷增加的現(xiàn)象趋箩。此問題多發(fā)于對象生命周期管理不當之處赃额,如持有對象引用過長,或未能及時釋放資源叫确,終致內(nèi)存枯竭跳芳,系統(tǒng)崩潰。

在JVM的世界中竹勉,內(nèi)存泄漏常見于以下幾種情況:

  • 靜態(tài)集合類:如 HashMap飞盆、ArrayList 等,若不斷向其添加對象而不清理次乓,易造成內(nèi)存泄漏吓歇。
  • 長生命周期對象持有短生命周期對象引用:如單例模式中的對象持有臨時對象引用,導致臨時對象無法被垃圾回收票腰。
  • 未關閉的資源:如數(shù)據(jù)庫連接城看、文件流等,若未及時關閉丧慈,亦會導致內(nèi)存泄漏析命。
  • 監(jiān)聽器與回調(diào)函數(shù):未及時移除的監(jiān)聽器或回調(diào)函數(shù),可能導致對象無法被回收逃默。

解決方案:內(nèi)存泄漏的破解之道

  1. 善用工具鹃愤,探查隱患
    如同俠客需借助兵器,程序員亦需運用內(nèi)存分析工具完域,如 jvisualvm软吐、jmap、jhat 等吟税,探查內(nèi)存使用情況凹耙,定位內(nèi)存泄漏之源。常規(guī)步驟如下:
    • 使用 jmap 生成堆轉儲文件:jmap -dump:live,format=b,file=heap_dump.hprof <pid>
    • 使用 jvisualvm 或 Eclipse MAT 分析堆轉儲文件肠仪,查找無法回收的對象肖抱。
  1. 優(yōu)化代碼,清理內(nèi)存
    針對發(fā)現(xiàn)的內(nèi)存泄漏問題异旧,需優(yōu)化代碼意述,確保對象在不再使用時盡快釋放。具體方法如下:
    • 及時清理集合:對于使用完畢的對象吮蛹,及時從集合中移除荤崇。
    • 合理管理對象引用:避免長生命周期對象持有短生命周期對象引用,可使用弱引用(WeakReference)來管理潮针。
    • 關閉資源:對于文件流术荤、數(shù)據(jù)庫連接等資源,在使用完畢后每篷,務必調(diào)用 close() 方法關閉瓣戚。
    • 移除監(jiān)聽器:在適當時機端圈,移除不再需要的監(jiān)聽器或回調(diào)函數(shù)。
      加強監(jiān)控子库,防患未然

如同江湖俠客需時刻警惕枫笛,程序員亦需持續(xù)監(jiān)控內(nèi)存使用情況,防患于未然刚照⌒糖桑可使用監(jiān)控工具如 Prometheus、Grafana 等无畔,實時查看內(nèi)存使用情況啊楚,及時發(fā)現(xiàn)異常。

滴滴滴浑彰、滴滴滴....,代碼劍宗中的某一處洞府中的告警聲不絕于耳恭理,近眼望去,洞府正中央的蒲團上正坐著一位雙眼緊閉身材健碩的男子郭变,此男子的旁邊還擺放著籃球颜价、杠鈴等道具,從洞府的擺布不難看出此男子平日經(jīng)常鍛煉诉濒,以至于他的身型較于常人更加挺拔高大周伦。男子聽到告警聲睜開雙眼,男子雙眸中充滿精光未荒,看來此次閉關男子有了不少收獲专挪。聽到警告聲的男子眼神中閃過了一絲不耐煩,嘴里輕輕碎了一聲片排,然后不緊不慢地從袖中拿出一個圓盤寨腔,此圓盤此時一直閃爍著紅光,并且一直發(fā)出“滴滴滴”的聲音率寡,男子用手輕撫手中圓盤迫卢,身前映射出一個巨大光影,光影里面有一些畫面跟文字冶共,男子大概花了一刻鐘時間掃描完光影里的內(nèi)容乾蛤。他臉上閃過一絲苦澀,然后說道:“該死的比默,竟然出現(xiàn)了內(nèi)存告警”幻捏,隨即只見男子雙手一揮把光影打散盆犁,男子站了起來朝著旁邊的一個房間走去命咐。

而洞府中的男子就是咱們的男主“阿強”。他之前正坐在蒲團上修煉內(nèi)功到一個關鍵時刻谐岁,不曾想被圓盤的告警打斷醋奠,此時的阿強心情不是很美麗.......

內(nèi)存緊急處理

阿強離開洞府的第一件事情就是通過告警身份牌進入“乾坤內(nèi)存法陣”查看告警的應用陣腳榛臼,阿強查看應用的內(nèi)存情況跟系統(tǒng)的一些指標如下圖所示,阿強看到這個內(nèi)存水位情況就發(fā)現(xiàn)了不對勁窜司。應用從晚上03:00開始到目前為止內(nèi)存一直處于一個緩慢上升狀態(tài)沛善。不過此時阿強倒也沒有因此慌了自己陣腳,阿強根據(jù)以前處理類似問題的經(jīng)驗塞祈,他先通過“乾坤內(nèi)存法陣”中的應用內(nèi)存Dump導出功能先將內(nèi)存快照給dump下來金刁,然后就將應用的容器進行重啟的操作。

實時區(qū)間熱點圖
[圖片上傳失敗...(image-83d0fd-1727321763931)]

實時線程數(shù)
[圖片上傳失敗...(image-adfc78-1727321763931)]
實時gc數(shù)量
[圖片上傳失敗...(image-f145ab-1727321763931)]
實時堆內(nèi)存信息
[圖片上傳失敗...(image-1a11c0-1727321763931)]
容器實時內(nèi)存情況
[圖片上傳失敗...(image-407905-1727321763931)]
不久议薪,應用的快照文件就dump了下來尤蛮,阿強看著dump下來的文件并沒有直接去分析而是優(yōu)先去詢問了負責此應用的人詢問了一下具體情況。2個時辰后斯议,阿強大概從負責此應用的人口中知道了此應用的基本情況产捞。此應用名叫G服務,是從F服務中拆出來的一個應用哼御,拆出來的G服務的代碼內(nèi)容與G服務是保持一致的坯临,但是G服務的內(nèi)存表現(xiàn)很穩(wěn)定,并沒有F服務表現(xiàn)出來的內(nèi)存緩慢爬升的情況恋昼,而G服務表現(xiàn)內(nèi)存緩慢爬升則是隨著不斷提高流量灰度的一同上升的看靠。其中F服務的一個容器內(nèi)存情況大致是8臺內(nèi)存16G的云服務器,G服務的容器內(nèi)存情況是4臺8G的云服務器液肌。還有一個值得注意的一個點則是G服務的調(diào)用鏈路由于處于流量切換的過程與在F服務中不同衷笋,其區(qū)別如:
[圖片上傳失敗...(image-92d011-1727321763931)]
其中橙色的線表示G服務從F服務拆分出來后多一次交互,也就是說矩屁,在流量切換灰度期間辟宗,G服務的流量入口是從F服務通過rpc接口方式接受的。

此時的阿強大概了解了G服務應用的基本情況吝秕,接下來要做的事情則是去分析內(nèi)存緩慢爬升的問題泊脐,只見他拿出了一法器,此法器名叫“乾坤內(nèi)存鏡”烁峭,此法器的作用就是能夠清晰地分析應用內(nèi)存快照文文件容客,在使用法器有一個細節(jié)問題需要注意的是,如果通過此法器直接去打開內(nèi)存快照文件约郁,此法器會默認進行fullgc缩挑,fullgc后的快照文件如下圖:
[圖片上傳失敗...(image-a7acf0-1727321763931)]
fullgc后的快照文件內(nèi)存大小明顯和實際占用不同,如果想讓法器打開快照文件不盡興fullgc鬓梅,則只需要換一種打開方式供置,打開方式如下:
[圖片上傳失敗...(image-49b3ea-1727321763931)]
[圖片上傳失敗...(image-2b658f-1727321763931)][圖片上傳失敗...(image-8c092-1727321763931)]
[圖片上傳失敗...(image-a51259-1727321763931)]
此時你應該能看到如下圖的內(nèi)容說明此次打開方式?jīng)]有盡興fullgc,我們只需要稍等片刻即可
[圖片上傳失敗...(image-3a5d83-1727321763931)]
通過這種方式打開的快照文件則是如下所示:
[圖片上傳失敗...(image-d7254c-1727321763931)]
阿強看著解析出來的快照內(nèi)容绽快,此時展現(xiàn)出來內(nèi)容是通過內(nèi)存的實例的數(shù)量來進行的排序芥丧,其中紧阔,char[]占用了1412m大小的內(nèi)存,粗略看下來沒有什么大對象续担。如果是幾年前的阿強擅耽,他會傻不拉幾地去查看char[]實例的引用,但是此時的阿強已經(jīng)不是兩年前的阿強物遇,經(jīng)過時長兩年半的練習乖仇,他踩過數(shù)不清的坑,經(jīng)驗告訴他询兴,此時你應該看看第三個實例,阿強此時查看第三個實例的Merged outgoing references这敬,他看到此實例的引用
[圖片上傳失敗...(image-e245da-1727321763931)]
然后再進一步跟進String的引用,除了spring的常規(guī)引用蕉朵,發(fā)現(xiàn)logback和jackson有引用大量的字符實例崔涂。
[圖片上傳失敗...(image-a6212b-1727321763931)]
[圖片上傳失敗...(image-6d5dff-1727321763931)]
阿強此時通過idea打開了G服務的代碼,開始查看起來jackson和logback的代碼使用點始衅,2個時辰后冷蚂,阿強發(fā)現(xiàn)了一些奇怪的日志打印,如下:

log.info("業(yè)務接口處理請求參數(shù)明文汛闸,{}, http request method:{}",
                JSON.toJSONString(request.getParams()), methodMapping.getMethod());

上面這種日志打印會將整個請求的入?yún)⒍即蛴〕鰜眚瑁乙淮握埱箢愃七@種打印全部請求入?yún)⒌娜杖罩敬蟾庞?~7次,而由于G服務的承載的業(yè)務請求報文都是比較大的诸老,也就是說隆夯,每一次請求過來,這種大日子的打印會打印好幾次别伏,而這些大而全的日志大部分內(nèi)容是沒用的蹄衷,并且每次打印生成的字符串每次都是不同的,也就是每次請求在堆內(nèi)存中生成6~7個大字符對象厘肮,這種大字符對象會在堆中頻繁創(chuàng)建愧口,會造成youngc很頻繁。而youngc過于頻繁會造成很多大字符對象進入老年代类茂,導致整個堆內(nèi)存不斷上升耍属。為了驗證自己的猜想,阿強嘗試著刪除G服務中這些大日志的打印巩检,最終發(fā)現(xiàn)內(nèi)存上升的情況有一定的改善(此時的內(nèi)存已經(jīng)不會出現(xiàn)緩慢爬坡的情況)厚骗,但是內(nèi)存表現(xiàn)相比較F服務還是沒有那么好的,因此阿強又只能進一步去分析內(nèi)存塊照文件兢哭,2刻鐘后领舰,阿強在線程ThreadLocal中發(fā)現(xiàn)很多大char[]數(shù)組的引用,而這些ThreadLocal都是由rpc線程所持有。
[圖片上傳失敗...(image-67126f-1727321763931)]
而rpc底層的序列化正是使用的jackson提揍,而com.fasterxml.jackson.core.util.BufferRecycler 是 Jackson 庫中的一個工具類,用于高效地管理和重用緩沖區(qū)煮仇。在多線程環(huán)境中劳跃,特別是使用 ThreadLocal 時,確實有可能導致內(nèi)存泄漏:

  1. ThreadLocal 的生命周期問題:ThreadLocal 變量會與線程的生命周期綁定浙垫,如果線程不被回收刨仑,ThreadLocal 變量及其引用的對象也不會被回收,可能導致內(nèi)存泄漏夹姥。
  2. 緩沖區(qū)的大小和數(shù)量:如果緩沖區(qū)的大小或數(shù)量非常大杉武,且這些緩沖區(qū)長期占用內(nèi)存而不被釋放,可能導致內(nèi)存使用過多辙售,從而引發(fā)內(nèi)存泄漏轻抱。
  3. 線程池使用不當:在使用線程池時,如果沒有正確管理線程池的生命周期和資源旦部,可能會導致線程無法被回收祈搜,進而導致 ThreadLocal 引用的對象無法被回收。

到這里真相大白士八,而阿強面對涉及基礎設施的改造容燕,他有點煩躁。凡是涉及基礎設施的改動婚度,任務的難度和解決時間就會成倍增加蘸秘,因為基礎設施的改造流程會拉的比較長。但這個任務是一個緊急的任務蝗茁,為了快速地將問題處理醋虏,那怎么能夠不去改造基礎設施并解決這個問題呢,阿強腦子在飛速的運轉哮翘,不多時灰粮,阿強心中閃過一絲光亮,他緊皺的眉間也開始舒坦忍坷。剛剛的那一絲光亮就是快速解決任務的關鍵粘舟,那就是:“類加載器的雙親委派機制!佩研!”

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末柑肴,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子旬薯,更是在濱河造成了極大的恐慌晰骑,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,744評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異硕舆,居然都是意外死亡秽荞,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評論 3 392
  • 文/潘曉璐 我一進店門抚官,熙熙樓的掌柜王于貴愁眉苦臉地迎上來扬跋,“玉大人,你說我怎么就攤上這事凌节∏仗” “怎么了?”我有些...
    開封第一講書人閱讀 163,105評論 0 353
  • 文/不壞的土叔 我叫張陵倍奢,是天一觀的道長朴上。 經(jīng)常有香客問我,道長卒煞,這世上最難降的妖魔是什么痪宰? 我笑而不...
    開封第一講書人閱讀 58,242評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮畔裕,結果婚禮上酵镜,老公的妹妹穿的比我還像新娘。我一直安慰自己柴钻,他們只是感情好淮韭,可當我...
    茶點故事閱讀 67,269評論 6 389
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著贴届,像睡著了一般靠粪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上毫蚓,一...
    開封第一講書人閱讀 51,215評論 1 299
  • 那天占键,我揣著相機與錄音,去河邊找鬼元潘。 笑死畔乙,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的翩概。 我是一名探鬼主播牲距,決...
    沈念sama閱讀 40,096評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼钥庇!你這毒婦竟也來了牍鞠?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,939評論 0 274
  • 序言:老撾萬榮一對情侶失蹤评姨,失蹤者是張志新(化名)和其女友劉穎难述,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,354評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡胁后,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,573評論 2 333
  • 正文 我和宋清朗相戀三年店读,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片攀芯。...
    茶點故事閱讀 39,745評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡屯断,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出敲才,到底是詐尸還是另有隱情裹纳,我是刑警寧澤择葡,帶...
    沈念sama閱讀 35,448評論 5 344
  • 正文 年R本政府宣布紧武,位于F島的核電站,受9級特大地震影響敏储,放射性物質(zhì)發(fā)生泄漏阻星。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,048評論 3 327
  • 文/蒙蒙 一已添、第九天 我趴在偏房一處隱蔽的房頂上張望妥箕。 院中可真熱鬧,春花似錦更舞、人聲如沸畦幢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宇葱。三九已至,卻和暖如春刊头,著一層夾襖步出監(jiān)牢的瞬間黍瞧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評論 1 269
  • 我被黑心中介騙來泰國打工原杂, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留印颤,地道東北人。 一個月前我還...
    沈念sama閱讀 47,776評論 2 369
  • 正文 我出身青樓穿肄,卻偏偏與公主長得像年局,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子咸产,可洞房花燭夜當晚...
    茶點故事閱讀 44,652評論 2 354

推薦閱讀更多精彩內(nèi)容