前段時間收到線上一些列告警,內容是CMSGC太頻繁。那接下來這篇文章我會告訴你:什麼是CMSGC太頻繁;整個排查過程與你分享;以及一些規避手段。 ...
大家好,我是陶朱公Boy。
背景
不知道大家看到這條告警內容後,是什麼感觸?我當時是一臉懵逼的,一萬個為什麼縈繞心頭。
什麼是CmsGc?CmsGc太頻繁又是什麼意思?什麼情況下會觸發CMSGC太頻繁這種告警?要怎麼樣去找到那個被頻繁創建的對象?最後又需要怎麼規避?
接下來這篇文章我會來回答一下:什麼是CMSGC太頻繁;整個排查過程與你分享;最後我們一起探討一下一些規避手段。
什麼是CMSGC太頻繁
首先我覺得還是有必要解釋清楚什麼是CMSGC太頻繁這個術語,相信不少小伙伴也是比較關心的。
如果你聽過垃圾搜集器中有一款名為CMS垃圾搜集器,那就好理解了,所謂的CMSGC太頻繁意思是說CMS垃圾搜集器在當下時間視窗垃圾收集的動作頻次太快(平時老半天才回收一次或幾次垃圾對象,現在可能一分鐘就需要回收多次),大致就是這個意思。
所以說CMS垃圾收集器是一款作用於老年代區域的垃圾收集器。
關於CMS+ParNew垃圾搜集器的配置說明:大家如果在VM啟動配置參數中做如下配置:-XX:+UseConcMarkSweepGC.該配置項首先是激活CMS收集器(作用於老年代)。之後-XX:UseParNewGC會自動開啟,意味著年輕代將使用多線程並行垃圾收集器parNew進行回收。
原因分析
-
新生代因為垃圾回收之後,因為存活對象太多,導致Survivor空間放不下,部分對象會進入老年代 -
大對象直接進入老年代
這裡的大對象是指那些需要大量連續空間的JAVA對象,比如那種很長的字元串或數組對象。
-
長期存活的對象將進入老年代
對象在Eden出生,並經過第一次YGC後任然存活,並且能被Survivor空間容納,將被移動到Survivor空間中,並且對象年齡設為1。對象在Survivor空間每熬過一次YGC,年齡就增加一歲,如果達到15(預設)歲,對象就會進入老年代。
-
動態對象年齡判斷
這點是對長期存活的對象進入老年代的補充。 其實不一定要必須滿足所謂的存活對象年齡達到15歲才能進入老年代。如果一次YGC後,儘管Survivor區域有空間能容納存活對象,但這批存活對象恰好存活的年齡相同,且加起來的大小總和大於Survivor空間的一半,這些對象照樣會進入老年代。
-
老年代可用的連續空間小於年輕代歷次YGC後升入老年代的對象總和的平均大小,說明YGC後升入老年代的對象大小很可能超過了老年底當期可用的記憶體空間;觸發cmsgc後再進行ygc
-
ygc之後有一批對象需要放入老年代,但老年代沒有足夠的空間存放了,需要觸發一次cmsgc
-
老年代的記憶體使用率超過92%,也要觸發OLD 過程(通過參數控制-xx:+CMSInitiatingOccupancyFraction)
排查過程
-
配置VM參數 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${LOGDIR}/ 虛擬機在OOM異常之後會自動生成一份dump文件在本地 。 -
執行jmap(Java記憶體映像工具)命令 jdk提供的命令行工具jmap能生成堆存儲快照,jmap -dump:format=b,file=heapdump.hprof {進程ID}
-
阿裡開源性能診斷工具:Arthas
接下來作者用本次告警dump下來的堆文件,用MAT工具給大家演示一下具體查找問題對象的全過程。
MAT是Memory Analyzer tool的縮寫,是一種快速,功能豐富的Java堆分析工具,能幫助你查找記憶體泄漏和減少記憶體消耗。 很多情況下,我們需要處理測試提供的hprof文件,分析記憶體相關問題,那麼MAT也絕對是不二之選。Eclipse可以下載插件結合使用,也可以作為一個獨立分析工具使用。 下載地址:eclipse.org/mat/downloa。如果安裝過程中可能會碰到版本過低的問題,需要安裝一下高版本JDK 比如11,最後設置一下安裝路徑即可。
打開堆文件
如果你已經成功安裝完MAT。進入首頁後就可以打開本地hprof文件了。
打開文件後,進入分析頁
-
Actions:
Histogram 列出每個類所對應的對象個數,以及所占用的記憶體大小;Dominator Tree 以占用總記憶體的百分比的方式來列舉出所有的實例對象,註意這個地方是直接列舉出的對應的對象而不是類,這個視圖是用來發現大記憶體對象的Top Consumers:按照類和包分組的方式展示出占用記憶體最大的一個對象Duplicate Classes:檢測由多個類載入器所載入的類信息(用來查找重覆的類)
-
Reports:
Leak Suspects:通過MAT自動分析當前記憶體泄露的主要原因
Top Components:Top組件,列出大於總堆1%的組件的報告 -
Step By Step:
Component Report:組件報告,分析屬於公共根包或類載入器的對象
關註上述兩個選項基本就能找到問題對象了。
解決方案
-
如果你的程式代碼書寫正常,純粹是真的應用流量太大,你部署的機器沒辦法抗住這波流量,這種情況發生CMSGC太頻繁概率就很大了,甚至最終會導致OOM異常。對這種情況也只能橫向擴充機器了,以均衡流量。 -
如果你的機器足夠,線上流量也正常,但也發生了cmsgc太頻繁,甚至OOM異常。那大概率是你的程式代碼有問題,導致老年代區域聚集了大量垃圾對象,垃圾回收線程頻繁回收那些無用的垃圾對象,最終可能還達不到回收的理想效果,那麼這個時候你不得不分析堆裡面被大量占據的對象,看看是不是程式代碼問題導致老年代被堆滿。 像作者文章開始出的這個案例,作者經過上述步驟分析後,發現是程式代碼問題導致有大量對象進入老年代。(作者在應用中引入了一個java8的Nashorn組件,該組件的構建過程極其複雜,內部會創建很多個對象實例,因為作者的業務流量還是比較大的,每秒2000+QPS),機器也是夠的大概10台(每台4C8G),分析發現記憶體中大量充斥著Nashorn相關代碼,經過深入分析,其實這個Nashorn實例全局單例就可以了,不需要每次方法執行都構建一個實例,因為構建過程複雜且多對象,流量一高勢必最終導致應用發生記憶體溢出等異常。
總結
最後我也總結了應該如何避免發生GC太頻繁甚至OOM這類異常。如果程式代碼一切正常,純粹是瞬時流量太高才導致的GC動作加快,可以考慮臨時增加伺服器實例,分攤流量。不過很多問題可能都是程式員代碼書寫不正確才導致的,這個時候你應該首先找出問題對象,然後找出頻繁創建對象的代碼塊。
本文完!
寫到最後
作為996的程式員,寫這篇文章基本都是利用工作日下班時間和周六周日雙休的時間才最終成稿,比較不易。 如果你看了文章之後但凡對你有所幫助或啟發,真誠懇請幫忙關註一下作者,點贊、在看此文。你的肯定與贊美是我未來創作最強大的動力,我也將繼續前行,創作出更加優秀好的作品回饋給大家,在此先謝謝大家了!關註我
如果這篇文章你看了對你有幫助或啟發,麻煩點贊、關註一下作者。你的肯定是作者創作源源不斷的動力。
公眾號
裡面不僅彙集了硬核的乾貨技術、還彙集了像左耳朵耗子、張朝陽總結的高效學習方法論、職場升遷竅門、軟技能。希望能輔助你達到你想夢想之地!
公眾號內回覆關鍵字“電子書”下載pdf格式的電子書籍(JAVAEE、Spring、JVM、併發編程、Mysql、Linux、kafka、分散式等)、“開發手冊”獲取阿裡開發手冊2本、"面試"獲取面試PDF資料。