案例描述
我們在做項(xiàng)目遷移后 (jdk1.6 ->1.7, 框架升級到spring,業(yè)務(wù)代碼不做修改)昌简,發(fā)現(xiàn)遷移后的一些instance的gcoverhead在某個特定的時間段(我們用「T」來表示)會突然飆高吟逝,最終出現(xiàn)了jvm heap memory被耗盡的情況案怯,導(dǎo)致應(yīng)用無法響應(yīng)。
案例分析
我們首先分析這類問題是什么原因造成的澎办。我們具體找了其中一臺機(jī)器查看metrics,發(fā)現(xiàn)jvm heap memory在「T」時間段驟降金砍,最終無法恢復(fù)局蚀。
我們查看了對應(yīng)時間段「T」的log,我們發(fā)現(xiàn)在這段期間有很多請求(我們用A代替)超時的異常恕稠,這類request會去一個service(我們用B代替)上拿資源琅绅。日志顯示「T」時間段A類請求的耗時特別長,50000ms的請求比比皆是鹅巍。我們開始以為A類請求是在請求大文件千扶,所以超時時間設(shè)置比較大,因?yàn)檫@個項(xiàng)目本身就是要進(jìn)行各類文件的操作骆捧。但從日志發(fā)現(xiàn)澎羞,這類文件也不過6M。這對于一個內(nèi)網(wǎng)之間的請求調(diào)用而言敛苇,不是什么壓力妆绞。所以我們猜測,是網(wǎng)絡(luò)波動或者B服務(wù)本身不穩(wěn)定導(dǎo)致的問題,。我們隨后查看了A請求超時時間設(shè)置括饶,為900000ms.
然后我們把目光轉(zhuǎn)移到了遷移前老的項(xiàng)目株茶,我們發(fā)現(xiàn)在該特定時段「T」也會出現(xiàn)請求超時這類錯誤,A請求的超時時間也為900000ms图焰。遷移前老的項(xiàng)目也會因?yàn)檫^高的gcoverhead導(dǎo)致個別instance內(nèi)存被耗盡启盛,但影響的范圍很小,對于個別instance出現(xiàn)異常技羔,dev/ops的job會負(fù)責(zé)重啟項(xiàng)目僵闯。而且老的項(xiàng)目對于這種超時異常導(dǎo)致的問題的承受能力是25k per hour,而遷移后的項(xiàng)目堕阔,僅僅是3.5k棍厂。
是什么差異導(dǎo)致這種不同的行為呢?我們先看一下遷移前后和遷移后的一些參數(shù)對比
參數(shù)的差異最終鎖定到y(tǒng)oung generation的占比超陆。我們拿到了項(xiàng)目的heap dump牺弹。對于ibm jdk1.6和open jdk1.7,兩者使用的都是分代垃圾回收器时呀,open jdk1.7里面就是我們熟悉的CMS张漂。我們通過圖片可以看出最后gcoverhead增高是由于old generation的頻繁full gc導(dǎo)致的。對于CMS垃圾回收器的原理谨娜,這里簡單描述一下航攒,一個實(shí)例一般最開始創(chuàng)建于young generation(默認(rèn)Survivor0 : Survivor1 : Eden = 1 : 1 : 8)的Eden區(qū),在這個區(qū)域會進(jìn)行young gc趴梢,觸發(fā)條件Eden區(qū)滿了漠畜。對象經(jīng)過若干次(java對象頭里面用4個bit來存儲gc標(biāo)記,最大次數(shù)是15坞靶,但針對CMS憔狞,默認(rèn)是4次。JVM 參數(shù)的設(shè)定為-XX:MaxTenuringThreshold 彰阴。這個參數(shù)和 -XX:TargetSurvivorRatio配合使用:期望s區(qū)存活大小的參數(shù)瘾敢。默認(rèn)值為50,即50%尿这。當(dāng)一個S區(qū)中所有的age對象的大小如果大于等于Desired survivor size簇抵,則重新計算threshold,以age和MaxTenuringThreshold兩者的最小值為準(zhǔn)射众。來決定是否要晉升到老年代young gc后如果還存活碟摆,會進(jìn)入老年代,當(dāng)老年代滿了叨橱,會觸發(fā)full gc抱既,full gc會STW,會消耗大量資源慕购。
所以看出舟舒,升級到老年代,不一定完全按照age ,如果object 增長過大, 甚至只經(jīng)過一次就直接進(jìn)入到老年代了。
從圖中我們可以看到Y(jié)oung generation中minior gc的頻率特別高厢洞,原因是我們遷移后的項(xiàng)目的young generation的占比過低(800m/6000m,一般推薦1:3)這樣就會導(dǎo)致一個問題典奉,對象過早晉升到年老代躺翻,從而觸發(fā)年老代頻繁的full gc。對應(yīng)到剛才的案例就是這些很占資源的長鏈接實(shí)例卫玖,經(jīng)過多次young gc一直沒有被回收 最終晉升到老年代 導(dǎo)致觸發(fā)頻繁的full gc 最終消耗盡jvm資源
解決辦法
在遷移后的項(xiàng)目里面公你,框架默認(rèn)young generation為800m,最終我們將遷移后的項(xiàng)目設(shè)定為2000m假瞬,當(dāng)出現(xiàn)這種大面積資源獲取緩慢時陕靠,項(xiàng)目不再出現(xiàn)抗不過的情況。至于為什么在timeout 900000ms這塊沒有進(jìn)行調(diào)優(yōu)脱茉,因?yàn)槟壳绊?xiàng)目是在遷移階段剪芥,我們是保持和老的項(xiàng)目相同的配置。而且問題我們有向用戶那邊反應(yīng)琴许,至于后續(xù)的更改税肪,由用戶來決定。
參考文檔
https://www.oracle.com/java/technologies/javase/vmoptions-jsp.html