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