摘要:結(jié)合的日志發(fā)現(xiàn)就算是發(fā)生了老年代也已經(jīng)回收不了,內(nèi)存已經(jīng)到頂。定位由于生產(chǎn)上的內(nèi)存文件非常大,達(dá)到了幾十。也是由于我們的內(nèi)存設(shè)置太大有關(guān)。同時(shí)后臺(tái)也開始打印內(nèi)存溢出了,這樣便復(fù)現(xiàn)出問題。結(jié)果發(fā)現(xiàn)類型的對(duì)象占用了將近的內(nèi)存。
前言
OutOfMemoryError 問題相信很多朋友都遇到過,相對(duì)于常見的業(yè)務(wù)異常(數(shù)組越界、空指針等)來說這類問題是很難定位和解決的。
本文以最近碰到的一次線上內(nèi)存溢出的定位、解決問題的方式展開;希望能對(duì)碰到類似問題的同學(xué)帶來思路和幫助。
主要從表現(xiàn)-->排查-->定位-->解決 四個(gè)步驟來分析和解決問題。
表象最近我們生產(chǎn)上的一個(gè)應(yīng)用不斷的爆出內(nèi)存溢出,并且隨著業(yè)務(wù)量的增長(zhǎng)出現(xiàn)的頻次越來越高。
該程序的業(yè)務(wù)邏輯非常簡(jiǎn)單,就是從 Kafka 中將數(shù)據(jù)消費(fèi)下來然后批量的做持久化操作。
而現(xiàn)象則是隨著 Kafka 的消息越多,出現(xiàn)的異常的頻次就越快。由于當(dāng)時(shí)還有其他工作所以只能讓運(yùn)維做重啟,并且監(jiān)控好堆內(nèi)存以及 GC 情況。
重啟大法雖好,可是依然不能根本解決問題。排查
于是我們想根據(jù)運(yùn)維之前收集到的內(nèi)存數(shù)據(jù)、GC 日志嘗試判斷哪里出現(xiàn)問題。
結(jié)果發(fā)現(xiàn)老年代的內(nèi)存使用就算是發(fā)生 GC 也一直居高不下,而且隨著時(shí)間推移也越來越高。
結(jié)合 jstat 的日志發(fā)現(xiàn)就算是發(fā)生了 FGC 老年代也已經(jīng)回收不了,內(nèi)存已經(jīng)到頂。
甚至有幾臺(tái)應(yīng)用 FGC 達(dá)到了上百次,時(shí)間也高的可怕。
這說明應(yīng)用的內(nèi)存使用肯定是有問題的,有許多賴皮對(duì)象始終回收不掉。
定位由于生產(chǎn)上的內(nèi)存 dump 文件非常大,達(dá)到了幾十G。也是由于我們的內(nèi)存設(shè)置太大有關(guān)。
所以導(dǎo)致想使用 MAT 分析需要花費(fèi)大量時(shí)間。
因此我們便想是否可以在本地復(fù)現(xiàn),這樣就要好定位的多。
為了盡快的復(fù)現(xiàn)問題,我將本地應(yīng)用最大堆內(nèi)存設(shè)置為 150M。
然后在消費(fèi) Kafka 那里 Mock 為一個(gè) while 循環(huán)一直不斷的生成數(shù)據(jù)。
同時(shí)當(dāng)應(yīng)用啟動(dòng)之后利用 VisualVM 連上應(yīng)用實(shí)時(shí)監(jiān)控內(nèi)存、GC 的使用情況。
結(jié)果跑了 10 幾分鐘內(nèi)存使用并沒有什么問題。根據(jù)圖中可以看出,每產(chǎn)生一次 GC 內(nèi)存都能有效的回收,所以這樣并沒有復(fù)現(xiàn)問題。
沒法復(fù)現(xiàn)問題就很難定位了。于是我們 review 代碼,發(fā)現(xiàn)生產(chǎn)的邏輯和我們用 while 循環(huán) Mock 數(shù)據(jù)還不太一樣。
查看生產(chǎn)的日志發(fā)現(xiàn)每次從 Kafka 中取出的都是幾百條數(shù)據(jù),而我們 Mock 時(shí)每次只能產(chǎn)生一條。
為了盡可能的模擬生產(chǎn)情況便在服務(wù)器上跑著一個(gè)生產(chǎn)者程序,一直源源不斷的向 Kafka 中發(fā)送數(shù)據(jù)。
果然不出意外只跑了一分多鐘內(nèi)存就頂不住了,觀察左圖發(fā)現(xiàn) GC 的頻次非常高,但是內(nèi)存的回收卻是相形見拙。
同時(shí)后臺(tái)也開始打印內(nèi)存溢出了,這樣便復(fù)現(xiàn)出問題。
解決從目前的表現(xiàn)來看就是內(nèi)存中有許多對(duì)象一直存在強(qiáng)引用關(guān)系導(dǎo)致得不到回收。
于是便想看看到底是什么對(duì)象占用了這么多的內(nèi)存,利用 VisualVM 的 HeapDump 功能可以立即 dump 出當(dāng)前應(yīng)用的內(nèi)存情況。
結(jié)果發(fā)現(xiàn) com.lmax.disruptor.RingBuffer 類型的對(duì)象占用了將近 50% 的內(nèi)存。
看到這個(gè)包自然就想到了 Disruptor 環(huán)形隊(duì)列。
再次 review 代碼發(fā)現(xiàn):從 Kafka 里取出的 700 條數(shù)據(jù)是直接往 Disruptor 里丟的。
這里也就能說明為什么第一次模擬數(shù)據(jù)沒復(fù)現(xiàn)問題了。
模擬的時(shí)候是一個(gè)對(duì)象放進(jìn)隊(duì)列里,而生產(chǎn)的情況是 700 條數(shù)據(jù)放進(jìn)隊(duì)列里。這個(gè)數(shù)據(jù)量是 700 倍的差距。
而 Disruptor 作為一個(gè)環(huán)形隊(duì)列,再對(duì)象沒有被覆蓋之前是一直存在的。
我也做了一個(gè)實(shí)驗(yàn),證明確實(shí)如此。
我設(shè)置隊(duì)列大小為 8 ,從 0~9 往里面寫 10 條數(shù)據(jù),當(dāng)寫到 8 的時(shí)候就會(huì)把之前 0 的位置覆蓋掉,后面的以此類推(類似于 HashMap 的取模定位)。
所以在生產(chǎn)上假設(shè)我們的隊(duì)列大小是 1024,那么隨著系統(tǒng)的運(yùn)行最終肯定會(huì)導(dǎo)致 1024 個(gè)位置上裝滿了對(duì)象,而且每個(gè)位置是 700 個(gè)!
于是查看了生產(chǎn)上 Disruptor 的 RingBuffer 配置,結(jié)果是:1024*1024。
這個(gè)數(shù)量級(jí)就非常嚇人了。
為了驗(yàn)證是否是這個(gè)問題,我在本地將該值換為 2 ,一個(gè)最小值試試。
同樣的 128M 內(nèi)存,也是通過 Kafka 一直源源不斷的取出數(shù)據(jù)。通過監(jiān)控如下:
跑了 20 幾分鐘系統(tǒng)一切正常,每當(dāng)一次 GC 都能回收大部分內(nèi)存,最終呈現(xiàn)鋸齒狀。
這樣問題就找到了,不過生產(chǎn)上這個(gè)值具體設(shè)置多少還得根據(jù)業(yè)務(wù)情況測(cè)試才能知道,但原有的 1024*1024 是絕對(duì)不能再使用了。
總結(jié)雖然到了最后也就改了一行代碼(還沒改,直接修改配置),但這排查過程我覺得是有意義的。
也會(huì)讓大部分覺得 JVM 這樣的黑盒難以下手的同學(xué)有一個(gè)直觀的感受。
同時(shí)也得感嘆 Disruptor 東西雖好,也不能亂用哦!
相關(guān)演示代碼查看:
https://github.com/crossoverJie/JCSprout/tree/master/src/main/java/com/crossoverjie/disruptor
你的點(diǎn)贊與轉(zhuǎn)發(fā)是最大的支持。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/76891.html
摘要:發(fā)現(xiàn)這是的一個(gè)堆棧,前段時(shí)間正好解決過一個(gè)由于隊(duì)列引起的一次強(qiáng)如也發(fā)生內(nèi)存溢出沒想到又來一出。因此初步判斷為大量線程執(zhí)行函數(shù)之后互相競(jìng)爭(zhēng)導(dǎo)致使用率增高,而通過對(duì)堆棧發(fā)現(xiàn)是和使用有關(guān)。 showImg(https://segmentfault.com/img/remote/1460000017395756?w=1816&h=1080); 前言 到了年底果然都不太平,最近又收到了運(yùn)維報(bào)警:...
摘要:結(jié)合之前的線程快照,我發(fā)現(xiàn)這個(gè)消費(fèi)線程也是處于狀態(tài),和后面的業(yè)務(wù)線程池一模一樣。本地模擬本地也是創(chuàng)建了一個(gè)單線程的線程池,分別執(zhí)行了兩個(gè)任務(wù)。發(fā)現(xiàn)當(dāng)任務(wù)中拋出一個(gè)沒有捕獲的異常時(shí),線程池中的線程就會(huì)處于狀態(tài),同時(shí)所有的堆棧都和生產(chǎn)相符。 showImg(https://segmentfault.com/img/remote/1460000018482477); 背景 事情(事故)是這樣...
摘要:我們知道是一個(gè)隊(duì)列,生產(chǎn)者往隊(duì)列里發(fā)布一項(xiàng)事件或稱之為消息也可以時(shí),消費(fèi)者能獲得通知如果沒有事件時(shí),消費(fèi)者被堵塞,直到生產(chǎn)者發(fā)布了新的事件。實(shí)戰(zhàn)本文先不具體去闡述的工作具體原理,只是簡(jiǎn)單地將與其整合。 什么是Disruptor 從功能上來看,Disruptor 是實(shí)現(xiàn)了隊(duì)列的功能,而且是一個(gè)有界隊(duì)列。那么它的應(yīng)用場(chǎng)景自然就是生產(chǎn)者-消費(fèi)者模型的應(yīng)用場(chǎng)合了。可以拿 JDK 的 Block...
摘要:純分享直接上干貨操作系統(tǒng)并發(fā)支持進(jìn)程管理內(nèi)存管理文件系統(tǒng)系統(tǒng)進(jìn)程間通信網(wǎng)絡(luò)通信阻塞隊(duì)列數(shù)組有界隊(duì)列鏈表無界隊(duì)列優(yōu)先級(jí)有限無界隊(duì)列延時(shí)無界隊(duì)列同步隊(duì)列隊(duì)列內(nèi)存模型線程通信機(jī)制內(nèi)存共享消息傳遞內(nèi)存模型順序一致性指令重排序原則內(nèi)存語義線程 純分享 , 直接上干貨! 操作系統(tǒng)并發(fā)支持 進(jìn)程管理內(nèi)存管...
閱讀 1734·2021-11-22 12:09
閱讀 1461·2019-08-30 13:22
閱讀 2094·2019-08-29 17:00
閱讀 2644·2019-08-29 16:28
閱讀 2956·2019-08-26 13:51
閱讀 1183·2019-08-26 13:25
閱讀 3245·2019-08-26 12:14
閱讀 3015·2019-08-26 12:14