一次線上故障之Java對(duì)象的生命歷程
“對(duì)象”的一生
像往常一樣,早上10點(diǎn)到了公司,趙小八打開電腦收到了PM前一天晚上發(fā)來(lái)的推薦系統(tǒng)新需求,內(nèi)心一萬(wàn)只草泥馬飄過(guò),思索了半天,打開IDEA開始了“愉快的”new對(duì)象之旅。
垃圾回收器老哥:你這樣瘋狂的嚯嚯對(duì)象,有考慮過(guò)我的感受嗎?
趙小八:你誰(shuí)啊?我new對(duì)象干你啥事?
垃圾回收器老哥:年輕人火氣別這么大,既然你這么說(shuō)那請(qǐng)耗子尾汁。
趙小八:呵,你哥我是被嚇大的
垃圾回收器老哥:年輕人不講武德...
沒兩天,小八翹著尾巴給PM說(shuō),功能上線了,剛沒一會(huì)兒PM罵罵咧咧的找來(lái)了,這tm為啥有時(shí)候能出來(lái)內(nèi)容有時(shí)候出不來(lái)啊,小八菊花一緊趕緊查起了問(wèn)題,先摟監(jiān)控接口平均耗時(shí)從200ms漲到了300ms,小八心想,我不過(guò)就多new了幾個(gè)對(duì)象,怎么tm的影響會(huì)這么大,同時(shí)DBA同學(xué)反饋資源監(jiān)控正常,看來(lái)只能摟業(yè)務(wù)日志看看了,可是業(yè)務(wù)日志也并沒有什么問(wèn)題,難道GC有問(wèn)題?果不其然,GC日志像瘋了一樣的刷日志。小八趕緊讓運(yùn)維緊急回滾線上代碼并dump了一份GC日志分析了起來(lái)。
現(xiàn)場(chǎng)代碼復(fù)原
上面這段代碼是一個(gè)簡(jiǎn)化版的用戶推薦系統(tǒng),真實(shí)情況下加載需要加載的物料除機(jī)器學(xué)習(xí)物料、商業(yè)物料外,還有其他各種例如:運(yùn)營(yíng)物料、曝光物料、關(guān)系物料等等。
當(dāng)一個(gè)真實(shí)用戶請(qǐng)求過(guò)來(lái)之后,上面提到的這些物料就需要全部被加載進(jìn)來(lái)。對(duì)象首先從新生代中被創(chuàng)建出來(lái),接著經(jīng)過(guò)一段時(shí)間GC后,最后存活下來(lái)的對(duì)象成功晉級(jí)到老年代,那么對(duì)象是在什么情況下成功晉級(jí)到老年代的呢?
case1:對(duì)象經(jīng)歷15次GC
- 小八瘋狂的new對(duì)象,此時(shí)新創(chuàng)建的都被分配到Eden區(qū),如下圖:
- 小八繼續(xù)瘋狂new對(duì)象,直到j(luò)vm老哥的Eden區(qū)放不下更多的對(duì)象了,于是觸發(fā)了一次youngGC,通過(guò)這次youngGC之后,只有Context1對(duì)象被回收,剩余存活對(duì)象進(jìn)入到了Survivor1里面,如下圖:

- 第一次youngGC結(jié)束后,小八又開始了new對(duì)象的神操作

- 沒一會(huì)兒,jvm又開始了youngGC,此時(shí)Eden區(qū)和Survivor1里面的存活對(duì)象全部移入到Survivor2中,剩余垃圾對(duì)象被回收。
- 就這樣反反復(fù)復(fù)經(jīng)歷了15次youngGC的折騰,還沒有被垃圾回收掉的對(duì)象最終進(jìn)入了Old區(qū)

case2:動(dòng)態(tài)年齡判斷
- 小八瘋狂的new對(duì)象

- 小八繼續(xù)瘋狂new對(duì)象,直到j(luò)vm老哥的Enden區(qū)放不下更對(duì)的對(duì)象了,于是觸發(fā)了一次youngGC

經(jīng)過(guò)此次youngGC后,剩余存活對(duì)象內(nèi)存占用大小超過(guò)了survivor1區(qū)大小的50%,比如:survivor1區(qū)大小為50M,而進(jìn)入到survivor1區(qū)的存活對(duì)象大小為30M,此時(shí)會(huì)將當(dāng)前存活時(shí)間最久的對(duì)象直接晉升到老年代(存活時(shí)間:經(jīng)歷過(guò)GC次數(shù)最多的對(duì)象),此時(shí)Context2對(duì)象和Context3對(duì)象進(jìn)入到老年代

case3:空間擔(dān)保機(jī)制
小八上線的用戶推薦系統(tǒng),JVM內(nèi)存的劃分情況為:整個(gè)堆大小為5G,其中老年代2.5G,新生代2.5G,其中新生代中Eden區(qū):Survivor區(qū)=8:2,即Eden區(qū)大小為2G,兩個(gè)Survivor區(qū)大小各為250M。
在晚高峰的時(shí)候一下子涌入1000人查看推薦列表,一個(gè)用戶消耗的JVM內(nèi)存達(dá)到了500kb,那么在一秒內(nèi)就消耗了500M,那么就意味著4秒鐘就會(huì)產(chǎn)生一次youngGC,假設(shè)每次GC后剩余的存活對(duì)象為300M,由于300M大小的存活對(duì)象無(wú)法在survivor區(qū)中存放下,此時(shí)就觸發(fā)了空間擔(dān)保機(jī)制。
- 小八瘋狂的new對(duì)象

- 直到發(fā)生第一次youngGC,但是一次youngGC后剩余的存活的對(duì)象大小Survivor區(qū)無(wú)法容納下,此時(shí)所有存活對(duì)象會(huì)直接進(jìn)入到Old區(qū)

在新生代沒有足夠的內(nèi)存存儲(chǔ)新產(chǎn)生的對(duì)象時(shí),老年代會(huì)判斷自己的區(qū)域剩余的內(nèi)存空間是否能夠放得下歷代youngGC后剩余存活對(duì)象(假設(shè)歷代youngGC剩余存活對(duì)象大小為300M),假設(shè)此時(shí)老年代還有1G大小的可用內(nèi)存,那么此次youngGC后剩余的存活對(duì)象將直接進(jìn)入到老年代;假設(shè)此時(shí)老年代剩余可用內(nèi)存大小為200M,那么就會(huì)觸發(fā)一次OldGC,OldGC完成后產(chǎn)生的空閑空間大于300M,此時(shí)會(huì)將新生代的存活對(duì)象放入老年代,如果OldGC后剩余的空閑空間小于300M,那么不好意思,就會(huì)拋出OOM了。
一圖總結(jié)Java對(duì)象流轉(zhuǎn)情況

上圖便是整個(gè)Java對(duì)象一生經(jīng)歷的流程,流程圖相對(duì)比較復(fù)雜一點(diǎn),從上往下對(duì)照前面講到的三種情況,相信還是比較容易理解的。
當(dāng)然圖中沒有畫圖新生代觸發(fā)OOM的情況,可以試想一下Eden區(qū)在什么時(shí)候會(huì)觸發(fā)OOM?答案在下篇文章給出。
總結(jié)
通過(guò)一個(gè)實(shí)際線上案例,講述了Java對(duì)象在不同情況下在JVM中經(jīng)歷的一生。通過(guò)本文大家可以嘗試將該流程套用到自己公司的項(xiàng)目里面,來(lái)分析自己負(fù)責(zé)的項(xiàng)目是否有類似的問(wèn)題,或者通過(guò)本篇文章來(lái)嘗試優(yōu)化自己的項(xiàng)目。另外本文的內(nèi)容可能會(huì)有某些地方講解的不合適,歡迎有問(wèn)題的朋友和我私聊探討。
在上篇文章中留了一個(gè)問(wèn)卷調(diào)查,結(jié)論如下:總投票人數(shù)7人,其中最想了解的技術(shù)是SpringCloud,最喜歡的分享方式是圖文結(jié)合。雖然投票人數(shù)比較少,但我相信投票的真實(shí)性,后續(xù)我會(huì)以這個(gè)結(jié)論為導(dǎo)向,分享更多實(shí)用的內(nèi)容給大家。
打個(gè)小廣告,年后大家有換個(gè)工作氛圍的朋友或者身邊有想法的朋友,快手研發(fā)、運(yùn)維、產(chǎn)品、運(yùn)營(yíng)全部崗位都有你想要的坑位,各種新業(yè)務(wù)發(fā)展速度快,機(jī)會(huì)多多,面試流程反饋速度超快,歡迎朋友們自薦或者推薦朋友來(lái)一起做點(diǎn)有意義的事。
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問(wèn)題,請(qǐng)聯(lián)系我們,謝謝!