017、大廠面試題:年輕代和老年代分別適合什么樣的垃圾回收算法?

大廠面試題
年輕代和老年代分別適合什么樣的垃圾回收算法?
目錄:
前文回顧
躲過(guò)15次GC之后進(jìn)入老年代
動(dòng)態(tài)對(duì)象年齡判斷
大對(duì)象直接進(jìn)入老年代
Minor GC后的對(duì)象太多,無(wú)法放入Survivor區(qū)怎么辦?
老年代空間分配擔(dān)保規(guī)則
老年代垃圾回收算法
昨日思考題解答
今日思考題
1、前文回顧
上篇文章已經(jīng)給大家講清楚了新生代的垃圾回收算法,包括跟這個(gè)垃圾回收算法搭配的新生代內(nèi)存區(qū)域的劃分,大家也都清楚了為什么有一塊Eden區(qū)域和兩塊Survivor區(qū)域
那么本文就要給大家說(shuō)說(shuō),新生代里的對(duì)象一般在什么場(chǎng)景下會(huì)進(jìn)入老年代。
首先我們來(lái)看下面的圖,我們寫(xiě)好的代碼在運(yùn)行的過(guò)程中,就會(huì)不斷的創(chuàng)建各種各樣的對(duì)象,這些對(duì)象都會(huì)優(yōu)先放到新生代的Eden區(qū)和Survivor1區(qū)。

接著假如新生代的Eden區(qū)和Survivor1區(qū)都快滿了,此時(shí)就會(huì)觸發(fā)Minor GC,把存活對(duì)象轉(zhuǎn)移到Survivor2區(qū)去
如下圖所示

然后接著就會(huì)使用Eden區(qū)和Survivor2區(qū),來(lái)分配新的對(duì)象,如下圖所示。

這個(gè)過(guò)程上篇文章已經(jīng)講的非常的清楚了。那么這篇文章我們就來(lái)依次看看各種情況下,對(duì)象是如何進(jìn)入老年代的,以及老年代的垃圾回收算法是什么樣的?
2、躲過(guò)15次GC之后進(jìn)入老年代
按照上面的圖示的那個(gè)過(guò)程,其實(shí)大家可以理解為我們寫(xiě)的系統(tǒng)剛啟動(dòng)的時(shí)候,創(chuàng)建的各種各樣的對(duì)象,都是分配在新生代里的。
然后慢慢系統(tǒng)跑著跑著,新生代就滿了,此時(shí)就會(huì)觸發(fā)Minor GC,可能就1%的少量存活對(duì)象轉(zhuǎn)移到空著的Survivor區(qū)中。
然后系統(tǒng)繼續(xù)運(yùn)行,繼續(xù)在Eden區(qū)里分配各種對(duì)象,大概就是這個(gè)過(guò)程。
那么之前給大家講過(guò),我們寫(xiě)的系統(tǒng)中有些對(duì)象是長(zhǎng)期存在的對(duì)象,他是不會(huì)輕易的被回收掉的,比如下面的代碼。

只要這個(gè)“Kafka”類還存在,那么他的靜態(tài)變量“replicaManager”就會(huì)長(zhǎng)期引用“ReplicaManager”對(duì)象,所以你無(wú)論新生代怎么垃圾回收,類似這種對(duì)象都不會(huì)被回收掉的。
此時(shí)這類對(duì)象每次在新生代里躲過(guò)一次GC被轉(zhuǎn)移到一塊Survivor區(qū)域中,此時(shí)他的年齡就會(huì)增長(zhǎng)一歲
默認(rèn)的設(shè)置下,當(dāng)對(duì)象的年齡達(dá)到15歲的時(shí)候,也就是躲過(guò)15次GC的時(shí)候,他就會(huì)轉(zhuǎn)移到老年代里去。
這個(gè)具體是多少歲進(jìn)入老年代,可以通過(guò)JVM參數(shù)“-XX:MaxTenuringThreshold”來(lái)設(shè)置,默認(rèn)是15歲,大家看下圖。

3、動(dòng)態(tài)對(duì)象年齡判斷
這里跟這個(gè)對(duì)象年齡有另外一個(gè)規(guī)則可以讓對(duì)象進(jìn)入老年代,不用等待15次GC過(guò)后才可以。
他的大致規(guī)則就是,假如說(shuō)當(dāng)前放對(duì)象的Survivor區(qū)域里,一批對(duì)象的總大小大于了這塊Survivor區(qū)域的內(nèi)存大小的50%,那么此時(shí)大于等于這批對(duì)象年齡的對(duì)象,就可以直接進(jìn)入老年代了。
說(shuō)著有點(diǎn)抽象,具體還是看圖。

假設(shè)這個(gè)圖里的Survivor2區(qū)有兩個(gè)對(duì)象,這倆對(duì)象的年齡一樣,都是2歲
然后倆對(duì)象加起來(lái)對(duì)象超過(guò)了50MB,超過(guò)了Survivor2區(qū)的100MB內(nèi)存大小的一半了,這個(gè)時(shí)候,Survivor2區(qū)里的大于等于2歲的對(duì)象,就要全部進(jìn)入老年代里去。
這就是所謂的動(dòng)態(tài)年齡判斷的規(guī)則,這條規(guī)則也會(huì)讓一些新生代的對(duì)象進(jìn)入老年代。
另外這里要理清楚一個(gè)概念,就是實(shí)際這個(gè)規(guī)則運(yùn)行的時(shí)候是如下的邏輯:年齡1+年齡2+年齡n的多個(gè)年齡對(duì)象總和超過(guò)了Survivor區(qū)域的50%,此時(shí)就會(huì)把年齡n以上的對(duì)象都放入老年代。
其實(shí)說(shuō)白了,無(wú)論是15歲的那個(gè)規(guī)則,還是動(dòng)態(tài)年齡判斷的規(guī)則,都是希望那些可能是長(zhǎng)期存活的對(duì)象,盡早進(jìn)入老年代
既然你是長(zhǎng)期存活的,那么老年代才是屬于你的地盤(pán),別賴在新生代里占地方了。
4、大對(duì)象直接進(jìn)入老年代
有一個(gè)JVM參數(shù),就是“-XX:PretenureSizeThreshold”,可以把他的值設(shè)置為字節(jié)數(shù),比如“1048576”字節(jié),就是1MB。
他的意思就是,如果你要?jiǎng)?chuàng)建一個(gè)大于這個(gè)大小的對(duì)象,比如一個(gè)超大的數(shù)組,或者是別的啥東西,此時(shí)就直接把這個(gè)大對(duì)象放到老年代里去。壓根兒不會(huì)經(jīng)過(guò)新生代。
之所以這么做,就是要避免新生代里出現(xiàn)那種大對(duì)象,然后屢次躲過(guò)GC,還得把他在兩個(gè)Survivor區(qū)域里來(lái)回復(fù)制多次之后才能進(jìn)入老年代,
那么大的一個(gè)對(duì)象在內(nèi)存里來(lái)回復(fù)制,不是很耗費(fèi)時(shí)間嗎?
所以說(shuō),這也是一個(gè)對(duì)象進(jìn)入老年代的規(guī)則。
5、Minor GC后的對(duì)象太多無(wú)法放入Survivor區(qū)怎么辦?
現(xiàn)在有一個(gè)比較大的問(wèn)題,就是如果在Minor GC之后發(fā)現(xiàn)剩余的存活對(duì)象太多了,沒(méi)辦法放入另外一塊Survivor區(qū)怎么辦?如下圖。

比如上面這個(gè)圖,假設(shè)在發(fā)生GC的時(shí)候,發(fā)現(xiàn)Eden區(qū)里超過(guò)150MB的存活對(duì)象,此時(shí)沒(méi)辦法放入Survivor區(qū)中,此時(shí)該怎么辦呢?
這個(gè)時(shí)候就必須得把這些對(duì)象直接轉(zhuǎn)移到老年代去,如下圖所示。

6、老年代空間分配擔(dān)保規(guī)則
這個(gè)時(shí)候大家又想提一個(gè)問(wèn)題了,如果新生代里有大量對(duì)象存活下來(lái),確實(shí)是自己的Survivor區(qū)放不下了,必須轉(zhuǎn)移到老年代去
那么如果老年代里空間也不夠放這些對(duì)象呢?這該咋整呢?
別急,一步一圖,跟著下面的圖來(lái)看。
首先,在執(zhí)行任何一次Minor GC之前,JVM會(huì)先檢查一下老年代可用的可用內(nèi)存空間,是否大于新生代所有對(duì)象的總大小。
為啥檢查這個(gè)呢?因?yàn)樽顦O端的情況下,可能新生代Minor GC過(guò)后,所有對(duì)象都存活下來(lái)了,那豈不是新生代所有對(duì)象全部要進(jìn)入老年代?如下圖。

如果說(shuō)發(fā)現(xiàn)老年代的內(nèi)存大小是大于新生代所有對(duì)象的,此時(shí)就可以放心大膽的對(duì)新生代發(fā)起一次Minor GC了,因?yàn)榧词筂inor GC之后所有對(duì)象都存活,Survivor區(qū)放不下了,也可以轉(zhuǎn)移到老年代去。
但是假如執(zhí)行Minor GC之前,發(fā)現(xiàn)老年代的可用內(nèi)存已經(jīng)小于了新生代的全部對(duì)象大小了
那么這個(gè)時(shí)候是不是有可能在Minor GC之后新生代的對(duì)象全部存活下來(lái),然后全部需要轉(zhuǎn)移到老年代去,但是老年代空間又不夠?
理論上,是有這種可能的。
所以假如Minor GC之前,發(fā)現(xiàn)老年代的可用內(nèi)存已經(jīng)小于了新生代的全部對(duì)象大小了,就會(huì)看一個(gè)“-XX:-HandlePromotionFailure”的參數(shù)是否設(shè)置了
如果有這個(gè)參數(shù),那么就會(huì)繼續(xù)嘗試進(jìn)行下一步判斷。
下一步判斷,就是看看老年代的內(nèi)存大小,是否大于之前每一次Minor GC后進(jìn)入老年代的對(duì)象的平均大小。
舉個(gè)例子,之前每次Minor GC后,平均都有10MB左右的對(duì)象會(huì)進(jìn)入老年代,那么此時(shí)老年代可用內(nèi)存大于10MB。
這就說(shuō)明,很可能這次Minor GC過(guò)后也是差不多10MB左右的對(duì)象會(huì)進(jìn)入老年代,此時(shí)老年代空間是夠的,看下圖。

如果上面那個(gè)步驟判斷失敗了,或者是“-XX:-HandlePromotionFailure”參數(shù)沒(méi)設(shè)置,此時(shí)就會(huì)直接觸發(fā)一次“Full GC”,就是對(duì)老年代進(jìn)行垃圾回收,盡量騰出來(lái)一些內(nèi)存空間,然后再執(zhí)行Minor GC。
如果上面兩個(gè)步驟都判斷成功了,那么就是說(shuō)可以冒點(diǎn)風(fēng)險(xiǎn)嘗試一下Minor GC。此時(shí)進(jìn)行Minor GC有幾種可能。
第一種可能,Minor GC過(guò)后,剩余的存活對(duì)象的大小,是小于Survivor區(qū)的大小的,那么此時(shí)存活對(duì)象進(jìn)入Survivor區(qū)域即可。
第二種可能,Minor GC過(guò)后,剩余的存活對(duì)象的大小,是大于 Survivor區(qū)域的大小,但是是小于老年代可用內(nèi)存大小的,此時(shí)就直接進(jìn)入老年代即可。
第三種可能,很不幸,Minor GC過(guò)后,剩余的存活對(duì)象的大小,大于了Survivor區(qū)域的大小,也大于了老年代可用內(nèi)存的大小。此時(shí)老年代都放不下這些存活對(duì)象了,就會(huì)發(fā)生“Handle Promotion Failure”的情況,這個(gè)時(shí)候就會(huì)觸發(fā)一次“Full GC”。
Full GC就是對(duì)老年代進(jìn)行垃圾回收,同時(shí)也一般會(huì)對(duì)新生代進(jìn)行垃圾回收。
因?yàn)檫@個(gè)時(shí)候必須得把老年代里的沒(méi)人引用的對(duì)象給回收掉,然后才可能讓Minor GC過(guò)后剩余的存活對(duì)象進(jìn)入老年代里面。
如果要是Full GC過(guò)后,老年代還是沒(méi)有足夠的空間存放Minor GC過(guò)后的剩余存活對(duì)象,那么此時(shí)就會(huì)導(dǎo)致所謂的“OOM”內(nèi)存溢出了
因?yàn)閮?nèi)存實(shí)在是不夠了,你還是要不停的往里面放對(duì)象,當(dāng)然就崩潰了。
這段規(guī)則有點(diǎn)燒腦,但是我覺(jué)得如果大家仔細(xì)對(duì)這段文字多看兩遍,然后結(jié)合我們的圖,腦子里想一想,基本都能看懂這個(gè)規(guī)則。
7、老年代垃圾回收算法
其實(shí)把上面的內(nèi)容都看懂之后,大家現(xiàn)在基本就知道了Minor GC的觸發(fā)時(shí)機(jī),然后就是Minor GC之前要對(duì)老年代空間大小做的檢查
包括檢查失敗的時(shí)候要提前觸發(fā)Full GC給老年代騰一些空間出來(lái),或者是Minor GC過(guò)后剩余對(duì)象太多放入老年代內(nèi)存都不夠,也要觸發(fā)Full GC。包括這套規(guī)則,還有觸發(fā)老年代垃圾回收的Full GC時(shí)機(jī),都給大家講清楚了。
簡(jiǎn)單來(lái)說(shuō),一句話總結(jié),對(duì)老年代觸發(fā)垃圾回收的時(shí)機(jī),一般就是兩個(gè):
要不然是在Minor GC之前,一通檢查發(fā)現(xiàn)很可能Minor GC之后要進(jìn)入老年代的對(duì)象太多了,老年代放不下,此時(shí)需要提前觸發(fā)Full GC然后再帶著進(jìn)行Minor GC;
要不然是在Minor GC之后,發(fā)現(xiàn)剩余對(duì)象太多放入老年代都放不下了。
那么對(duì)老年代進(jìn)行垃圾回收采用的是什么算法呢?
簡(jiǎn)單來(lái)說(shuō),老年代采取的是標(biāo)記整理算法,這個(gè)過(guò)程說(shuō)起來(lái)比較簡(jiǎn)單
大家看下圖,首先標(biāo)記出來(lái)老年代當(dāng)前存活的對(duì)象,這些對(duì)象可能是東一個(gè)西一個(gè)的。

接著會(huì)讓這些存活對(duì)象在內(nèi)存里進(jìn)行移動(dòng),把存活對(duì)象盡量都挪動(dòng)到一邊去,讓存活對(duì)象緊湊的靠在一起,避免垃圾回收過(guò)后出現(xiàn)過(guò)多的內(nèi)存碎片
然后再一次性把垃圾對(duì)象都回收掉,大家看下圖。

大家一定要注意一點(diǎn),這個(gè)老年代的垃圾回收算法的速度至少比新生代的垃圾回收算法的速度慢10倍。
如果系統(tǒng)頻繁出現(xiàn)老年代的Full GC垃圾回收,會(huì)導(dǎo)致系統(tǒng)性能被嚴(yán)重影響,出現(xiàn)頻繁卡頓的情況。
所以后面用各種案例給大家展現(xiàn)出來(lái)的,就是在各種業(yè)務(wù)系統(tǒng)的生產(chǎn)故障下,怎么去一步一步分析到底為什么頻繁的Full GC,然后怎么來(lái)調(diào)整JVM的各種參數(shù)進(jìn)行優(yōu)化。
其實(shí)大家如果透徹理解了最近的幾篇文章涵蓋的JVM的運(yùn)行原理,就會(huì)知道,所謂JVM優(yōu)化,就是盡可能讓對(duì)象都在新生代里分配和回收,盡量別讓太多對(duì)象頻繁進(jìn)入老年代,避免頻繁對(duì)老年代進(jìn)行垃圾回收,同時(shí)給系統(tǒng)充足的內(nèi)存大小,避免新生代頻繁的進(jìn)行垃圾回收。
關(guān)于如何優(yōu)化JVM,后續(xù)會(huì)有大量的案例帶著大家去實(shí)戰(zhàn),而且會(huì)給出模擬生產(chǎn)的代碼,讓大家運(yùn)行起來(lái)看到模擬出來(lái)的案發(fā)現(xiàn)場(chǎng)是如何導(dǎo)致JVM頻繁GC的,對(duì)性能是如何影響的,然后再一步一步來(lái)優(yōu)化JVM參數(shù)解決性能問(wèn)題。
8、昨日思考題
各位同學(xué)還記得之前教給過(guò)大家的那個(gè)系統(tǒng)對(duì)內(nèi)存使用壓力的估算方法么?可以借助那個(gè)方法估算一下,每秒鐘系統(tǒng)會(huì)使用多少內(nèi)存空間,然后多長(zhǎng)時(shí)間會(huì)觸發(fā)一次垃圾回收,垃圾回收之后,你們系統(tǒng)內(nèi)大體會(huì)有多少對(duì)象存活下來(lái)?為什么?都有哪些對(duì)象會(huì)存活下來(lái)?存活下來(lái)的對(duì)象會(huì)占多少內(nèi)存空間?
昨天留了這個(gè)思考題,希望大家跟著文章的思路去好好分析自己手頭寫(xiě)的代碼,從JVM角度去理解你的代碼是如何運(yùn)行的。
9、今日思考題
今天的思考題算是一個(gè)小作業(yè),今天的文章稍微有點(diǎn)難,有點(diǎn)燒腦,但是我覺(jué)得順著文章思路和大量的圖示,每個(gè)人是可以看懂的。
只不過(guò)消化起來(lái)需要點(diǎn)時(shí)間,希望大家今天能夠花點(diǎn)時(shí)間,梳理出來(lái)GC的全流程。
到底什么時(shí)候會(huì)嘗試觸發(fā)Minor GC?
觸發(fā)Minor GC之前會(huì)如何檢查老年代大小,涉及哪幾個(gè)步驟和條件?
什么時(shí)候在Minor GC之前就會(huì)提前觸發(fā)一次Full GC?
Full GC的算法是什么?
Minor GC過(guò)后可能對(duì)應(yīng)哪幾種情況?
哪些情況下Minor GC后的對(duì)象會(huì)進(jìn)入老年代?
希望大家自己通過(guò)畫(huà)圖的形式,把這個(gè)過(guò)程完整的梳理出來(lái),對(duì)這個(gè)過(guò)程的透徹理解,會(huì)成為我們后續(xù)進(jìn)行JVM優(yōu)化實(shí)戰(zhàn)的核心基石。
End
版權(quán):公眾號(hào)儒猿技術(shù)窩
未經(jīng)許可不得傳播,如有侵權(quán)將追究法律責(zé)任