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

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

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

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

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

經(jīng)過此次youngGC后,剩余存活對象內(nèi)存占用大小超過了survivor1區(qū)大小的50%,比如:survivor1區(qū)大小為50M,而進(jìn)入到survivor1區(qū)的存活對象大小為30M,此時(shí)會(huì)將當(dāng)前存活時(shí)間最久的對象直接晉升到老年代(存活時(shí)間:經(jīng)歷過GC次數(shù)最多的對象),此時(shí)Context2對象和Context3對象進(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后剩余的存活對象為300M,由于300M大小的存活對象無法在survivor區(qū)中存放下,此時(shí)就觸發(fā)了空間擔(dān)保機(jī)制。
- 小八瘋狂的new對象

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

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

上圖便是整個(gè)Java對象一生經(jīng)歷的流程,流程圖相對比較復(fù)雜一點(diǎn),從上往下對照前面講到的三種情況,相信還是比較容易理解的。
當(dāng)然圖中沒有畫圖新生代觸發(fā)OOM的情況,可以試想一下Eden區(qū)在什么時(shí)候會(huì)觸發(fā)OOM?答案在下篇文章給出。
總結(jié)
通過一個(gè)實(shí)際線上案例,講述了Java對象在不同情況下在JVM中經(jīng)歷的一生。通過本文大家可以嘗試將該流程套用到自己公司的項(xiàng)目里面,來分析自己負(fù)責(zé)的項(xiàng)目是否有類似的問題,或者通過本篇文章來嘗試優(yōu)化自己的項(xiàng)目。另外本文的內(nèi)容可能會(huì)有某些地方講解的不合適,歡迎有問題的朋友和我私聊探討。
在上篇文章中留了一個(gè)問卷調(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è)務(wù)發(fā)展速度快,機(jī)會(huì)多多,面試流程反饋速度超快,歡迎朋友們自薦或者推薦朋友來一起做點(diǎn)有意義的事。
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場,如有問題,請聯(lián)系我們,謝謝!