Disruptor的內(nèi)存溢出實(shí)例分析,很多新手對(duì)此不是很清楚,為了幫助大家解決這個(gè)難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來(lái)學(xué)習(xí)下,希望你能有所收獲。
創(chuàng)新互聯(lián)專注于景德鎮(zhèn)企業(yè)網(wǎng)站建設(shè),響應(yīng)式網(wǎng)站開(kāi)發(fā),商城網(wǎng)站建設(shè)。景德鎮(zhèn)網(wǎng)站建設(shè)公司,為景德鎮(zhèn)等地區(qū)提供建站服務(wù)。全流程按需定制網(wǎng)站,專業(yè)設(shè)計(jì),全程項(xiàng)目跟蹤,創(chuàng)新互聯(lián)專業(yè)和態(tài)度為您提供的服務(wù)
前言
OutOfMemoryError 問(wèn)題相信很多朋友都遇到過(guò),相對(duì)于常見(jiàn)的業(yè)務(wù)異常(數(shù)組越界、空指針等)來(lái)說(shuō)這類問(wèn)題是很難定位和解決的。
下面以最近碰到的一次線上內(nèi)存溢出的定位、解決問(wèn)題的方式展開(kāi)。
主要從表現(xiàn)-->排查-->定位-->解決 四個(gè)步驟來(lái)分析和解決問(wèn)題。
表象
最近我們生產(chǎn)上的一個(gè)應(yīng)用不斷的爆出內(nèi)存溢出,并且隨著業(yè)務(wù)量的增長(zhǎng)出現(xiàn)的頻次越來(lái)越高。
該程序的業(yè)務(wù)邏輯非常簡(jiǎn)單,就是從 Kafka 中將數(shù)據(jù)消費(fèi)下來(lái)然后批量的做持久化操作。
而現(xiàn)象則是隨著 Kafka 的消息越多,出現(xiàn)的異常的頻次就越快。由于當(dāng)時(shí)還有其他工作所以只能讓運(yùn)維做重啟,并且監(jiān)控好堆內(nèi)存以及 GC 情況。
重啟大法雖好,可是依然不能根本解決問(wèn)題。
排查
于是我們想根據(jù)運(yùn)維之前收集到的內(nèi)存數(shù)據(jù)、GC 日志嘗試判斷哪里出現(xiàn)問(wèn)題。
結(jié)果發(fā)現(xiàn)老年代的內(nèi)存使用就算是發(fā)生 GC 也一直居高不下,而且隨著時(shí)間推移也越來(lái)越高。
結(jié)合 jstat 的日志發(fā)現(xiàn)就算是發(fā)生了 FGC 老年代也已經(jīng)回收不了,內(nèi)存已經(jīng)到頂。
甚至有幾臺(tái)應(yīng)用 FGC 達(dá)到了上百次,時(shí)間也高的可怕。
這說(shuō)明應(yīng)用的內(nèi)存使用肯定是有問(wèn)題的,有許多賴皮對(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)問(wè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)存使用并沒(méi)有什么問(wèn)題。根據(jù)圖中可以看出,每產(chǎn)生一次 GC 內(nèi)存都能有效的回收,所以這樣并沒(méi)有復(fù)現(xiàn)問(wèn)題。
沒(méi)法復(fù)現(xiàn)問(wè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)存的回收卻是相形見(jiàn)拙。
同時(shí)后臺(tái)也開(kāi)始打印內(nèi)存溢出了,這樣便復(fù)現(xiàn)出問(wèn)題。
解決
從目前的表現(xiàn)來(lái)看就是內(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 里丟的。
這里也就能說(shuō)明為什么第一次模擬數(shù)據(jù)沒(méi)復(fù)現(xiàn)問(wè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ì)象沒(méi)有被覆蓋之前是一直存在的。
我也做了一個(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è)問(wèn)題,我在本地將該值換為 2 ,一個(gè)最小值試試。
同樣的 128M 內(nèi)存,也是通過(guò) Kafka 一直源源不斷的取出數(shù)據(jù)。通過(guò)監(jiān)控如下:
跑了 20 幾分鐘系統(tǒng)一切正常,每當(dāng)一次 GC 都能回收大部分內(nèi)存,最終呈現(xiàn)鋸齒狀。
這樣問(wèn)題就找到了,不過(guò)生產(chǎn)上這個(gè)值具體設(shè)置多少還得根據(jù)業(yè)務(wù)情況測(cè)試才能知道,但原有的 1024*1024 是絕對(duì)不能再使用了。
雖然到了最后也就改了一行代碼(還沒(méi)改,直接修改配置),但這排查過(guò)程我覺(jué)得是有意義的。
也會(huì)讓大部分覺(jué)得 JVM 這樣的黑盒難以下手的同學(xué)有一個(gè)直觀的感受。
看完上述內(nèi)容是否對(duì)您有幫助呢?如果還想對(duì)相關(guān)知識(shí)有進(jìn)一步的了解或閱讀更多相關(guān)文章,請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝您對(duì)創(chuàng)新互聯(lián)的支持。