背景
事故發(fā)生在某個(gè)工作日下午堪置,初診癥狀為頁面響應(yīng)時(shí)間巨長(zhǎng)抢埋,沒有任何反應(yīng)寇损,頁面一直處于加載狀態(tài)中,根據(jù)后端監(jiān)控平臺(tái)顯示鞭铆,相關(guān)服務(wù)的所有實(shí)例CPU占用率達(dá)到100%
,為了快速恢復(fù)服務(wù)焦影,及時(shí)止損车遂,果斷決定在負(fù)載均衡后,依次重啟一半的實(shí)例斯辰,然后再進(jìn)一步觀察是否能處理正常的業(yè)務(wù)流量舶担,不幸中的萬幸,這招管用了彬呻,如果不管用衣陶,可能就需要通過藍(lán)綠部署
進(jìn)行回滾操作了(但這樣會(huì)將最新版本的一些功能進(jìn)行回滾)。但這種管用是臨時(shí)還是永久的呢闸氮?
根因分析
難道萬能的重啟大法能治百布艨觥?當(dāng)然不是湖苞,任何一次產(chǎn)品事故都是難道的學(xué)習(xí)機(jī)會(huì)拯欧,往往這樣的經(jīng)驗(yàn)還非常的寶貴,于是根因分析
必不可少财骨。我們很快在服務(wù)暫時(shí)
恢復(fù)正常后镐作,即刻開始了進(jìn)一步的調(diào)查工作?通過分布式追蹤平臺(tái)隆箩,很快定位到了調(diào)用鏈上面有問題的服務(wù)该贾,針對(duì)有問題的服務(wù)進(jìn)行進(jìn)一步分析,CPU占用率100%
往往只是表象捌臊,導(dǎo)致它的原因才是我們真正關(guān)心的點(diǎn)杨蛋;如下圖,根據(jù)可視化的日志,發(fā)現(xiàn)頻繁的Full GC
占用了大量的CPU時(shí)間片逞力,并且Stop the world
曙寡,掛起了應(yīng)用進(jìn)程,導(dǎo)致服務(wù)可用性的降低:
在GC策略上面寇荧,我們的服務(wù)默認(rèn)都是選擇的CMS
举庶,在可達(dá)性分析的時(shí)候, 一般會(huì)經(jīng)歷下面三個(gè)階段:
-
初始標(biāo)記
(只標(biāo)記與GC Root直接相關(guān)的節(jié)點(diǎn),單GC線程, 短暫的應(yīng)用停頓) -
并發(fā)標(biāo)記
(GC進(jìn)程和業(yè)務(wù)進(jìn)程并發(fā)運(yùn)行揩抡,沒有停頓應(yīng)用) -
重新標(biāo)記
(防止并發(fā)標(biāo)記過程中户侥,業(yè)務(wù)進(jìn)程的更改,并行重新標(biāo)記和修復(fù)峦嗤,短暫的應(yīng)用停頓)
整個(gè)過程蕊唐,對(duì)比其他類型的GC回收策略(除G1外,JAVA9的默認(rèn)策略)烁设,CMS減少回收停頓時(shí)間替梨,應(yīng)用停頓時(shí)間應(yīng)該更短,可用性更高署尤。
那是什么導(dǎo)致的Full GC呢耙替?
- 顯示調(diào)用system.gc()?
- 內(nèi)存不足?
- 統(tǒng)計(jì)得到的Minor GC晉升到老年代的平均大小大于老年代的剩余空間曹体?
診斷結(jié)果為內(nèi)存不足
造成的俗扇,那么內(nèi)存不足又分為不同類型的內(nèi)存,比如JVM 堆內(nèi)存(GC主要回收的地方)箕别,棧內(nèi)存铜幽,Native內(nèi)存。
更加進(jìn)一步的分析為metaspace
串稀,Java8后引入了metaspace
替換掉了持久代
除抛,最主要的不同在于metaspace
是直接在Native內(nèi)存里面分配,不在heap上面母截,默認(rèn)可以動(dòng)態(tài)伸縮到忽,具有更高的自由度,具體大小與主機(jī)內(nèi)存有關(guān)清寇,當(dāng)然你也可以通過下面參數(shù)設(shè)置它的大小-XX:MaxMetaspaceSize
喘漏。
如下圖, metaspace
的空間隨著時(shí)間的推移华烟,逐步增加翩迈,直到服務(wù)器重啟后才得到釋放。
為什么會(huì)造成metaspace leak盔夜?
根據(jù)上面的我們掌握的信息负饲,我們已經(jīng)可以斷定堤魁,同樣的問題,隨著時(shí)間的推移返十,還會(huì)不斷重現(xiàn)妥泉。由于我們進(jìn)行了如下的步驟:
- 問題重現(xiàn),在產(chǎn)品環(huán)境下吧慢,將產(chǎn)品日志經(jīng)過一定清洗之后(保證操作不會(huì)產(chǎn)生臟數(shù)據(jù)或者副作用)涛漂,下載下來,通過Jmeter模擬多用戶并發(fā)訪問的真實(shí)場(chǎng)景检诗,訪問Inactive的服務(wù)實(shí)例,隨著訪問量的上升瓢剿,加速事故重現(xiàn)的概率逢慌。
- 內(nèi)存泄露分析,多次下載heap dump间狂, 分析泄露的classes或者classloaders攻泼,最后發(fā)現(xiàn)了大量的類似class,被重復(fù)加載鉴象。
- 二分法排錯(cuò)忙菠, 在代碼級(jí)別上面,進(jìn)行二分法排錯(cuò)纺弊,最后鎖定了問題牛欢。
根本原因:在某次發(fā)布時(shí),升級(jí)了一個(gè)包淆游,那個(gè)包里面包含了一個(gè)很老版本的JAXB庫傍睹,由于歷史原因沒有遵守maven version的規(guī)范,它的version類似2.X.X.1犹菱,而不是正常的release version格式:major.minor.bugfix拾稳,于是maven在處理版本沖突的時(shí)候,選擇了較老版本的庫腊脱,最后這個(gè)庫访得,在具體的業(yè)務(wù)場(chǎng)景下,不斷的重復(fù)創(chuàng)建并加載相關(guān)的類信息陕凹,不斷占用metaspace的空間, 導(dǎo)致Full GC悍抑,造成100%CPU。
參考
https://books.sonatype.com/mvnref-book/reference/pom-relationships-sect-pom-syntax.html
https://docs.oracle.com/middleware/1212/core/MAVEN/maven_version.htm#MAVEN8855