016、大廠面試題:JVM中有哪些垃圾回收算法,每個(gè)算法各自的優(yōu)劣?

大廠面試題
JVM中有哪些垃圾回收算法,每個(gè)算法各自的優(yōu)劣?
目錄:
1、前文回顧
2、復(fù)制算法的背景引入
3、一種不太好的垃圾回收思路
4、一個(gè)合理的垃圾回收思路
5、復(fù)制算法有什么缺點(diǎn)?
6、復(fù)制算法的優(yōu)化:Eden區(qū)和Survivor區(qū)
7、新生代垃圾回收的各種“萬一”怎么處理?
8、昨日思考題解答
9、今日思考題
1、前文回顧
上一篇文章我們重新梳理了一下什么時(shí)候觸發(fā)垃圾回收,以及到底哪些對(duì)象可以垃圾回收
另外,對(duì)新生代填滿,GC Roots對(duì)象,軟引用、弱引用,還有finalize()等概念進(jìn)行了比較細(xì)致的梳理。
那么這篇文章,我們就來看看在對(duì)新生代進(jìn)行垃圾回收的時(shí)候,到底是采取一種什么樣的算法進(jìn)行的呢?
2、復(fù)制算法的背景引入
針對(duì)新生代的垃圾回收算法,他叫做復(fù)制算法
簡(jiǎn)單來說,就是如下圖所示,首先把新生代的內(nèi)存分為兩塊。

接著假設(shè)有如下代碼,在“l(fā)oadReplicasFromDisk()”方法中創(chuàng)建了對(duì)象,此時(shí)對(duì)象就就會(huì)分配在新生代其中一塊內(nèi)存空間里
而且是由“main線程”的棧內(nèi)存中的“l(fā)oadReplicasFromDisk()”方法的棧幀內(nèi)的局部變量來引用的,如下圖所示。


接著大家想象一下,假設(shè)與此同時(shí),代碼在不停的運(yùn)行,然后大量的對(duì)象都分配在了新生代內(nèi)存的其中一塊內(nèi)存區(qū)域里,也只會(huì)分配在那塊區(qū)域里
而且分配過后,很快就失去了局部變量或者類靜態(tài)變量的引用,成為了垃圾對(duì)象
此時(shí)如下圖所示。

接著這個(gè)時(shí)候,新生代內(nèi)存那塊被分配對(duì)象的內(nèi)存區(qū)域基本都快滿了,再次要分配對(duì)象的時(shí)候,發(fā)現(xiàn)里面內(nèi)存空間不足了。
那么此時(shí)就會(huì)觸發(fā)Minor GC去回收掉新生代那塊被使用的內(nèi)存空間的垃圾對(duì)象。
那么回收的時(shí)候是怎么做的呢?
3、一種不太好的垃圾回收思路
假設(shè)現(xiàn)在采用的垃圾回收思路,就是直接對(duì)上圖中被使用的那塊內(nèi)存區(qū)域中的垃圾對(duì)象進(jìn)行標(biāo)記。
也就是根據(jù)上篇文章講的那套思路,標(biāo)記出哪些對(duì)象是可以被垃圾回收的,然后就直接對(duì)那塊內(nèi)存區(qū)域中的對(duì)象進(jìn)行垃圾回收,把內(nèi)存空出來。
大家想想,這種思路好嗎?
這種思路去垃圾回收,可能會(huì)在回收完畢之后造成那塊內(nèi)存區(qū)域看起來跟下圖一樣。

看上面的圖,不知道大家發(fā)現(xiàn)什么沒有,在那塊被使用的內(nèi)存區(qū)域里,回收掉了大量的垃圾對(duì)象,但是保留了一些被人引用的存活對(duì)象
但是呢,存活對(duì)象在內(nèi)存區(qū)域里東一個(gè)西一個(gè),非常的凌亂,而且造成了大量的內(nèi)存碎片。
那么什么是內(nèi)存碎片呢?
我們?cè)倏聪旅娴膱D我用紅線標(biāo)記出來的區(qū)域,那些就是所謂的內(nèi)存碎片。

看到了嗎?在各種凌亂的存活對(duì)象的中間,出現(xiàn)了大量的紅圈圈出來的內(nèi)存碎片
這些內(nèi)存碎片的大小不一樣,有的可能很大,有的可能很小。
那么內(nèi)存碎片太多會(huì)造成什么問題呢?
內(nèi)存浪費(fèi)
啥意思?比如現(xiàn)在打算分配一個(gè)新的對(duì)象,嘗試在上圖那塊被使用的內(nèi)存區(qū)域里去分配
此時(shí)如下圖所示,可能因?yàn)閮?nèi)存碎片太多的緣故,雖然所有的內(nèi)存碎片加起來其實(shí)有很大的一塊內(nèi)存,但是因?yàn)檫@些內(nèi)存都是碎片式分散的,所以導(dǎo)致沒有一塊完整的足夠的內(nèi)存空間來分配新的對(duì)象。

所以這種直接對(duì)一塊內(nèi)存空間回收掉垃圾對(duì)象,保留存活對(duì)象的方法,絕對(duì)是不可取的
因?yàn)閮?nèi)存碎片太多,就是他最大的問題,會(huì)造成大量的內(nèi)存浪費(fèi),很多內(nèi)存碎片壓根兒是沒法使用的。
4、一個(gè)合理的垃圾回收思路
那么能不能用一種合理的思路來進(jìn)行垃圾回收呢?
可以!這個(gè)時(shí)候上圖中一直沒派上用場(chǎng)的另外一塊空白的內(nèi)存區(qū)域就出場(chǎng)了。
首先,并不是按照上述思路直接對(duì)已經(jīng)使用的那塊內(nèi)存把垃圾對(duì)象全部回收掉,然后保留全部存活對(duì)象。
而是先對(duì)那塊在使用的內(nèi)存空間標(biāo)記出里面哪些對(duì)象是不能進(jìn)行垃圾回收的,就是要存活的對(duì)象
然后先把那些存活的對(duì)象轉(zhuǎn)移到另外一塊空白的內(nèi)存中,如下圖。

不知道大家發(fā)現(xiàn)這里的玄機(jī)沒有?
沒錯(cuò),通過把存活對(duì)象先轉(zhuǎn)移到另外一塊空白內(nèi)存區(qū)域,我們可以把這些對(duì)象都比較緊湊的排列在內(nèi)存里
這樣就可以讓被轉(zhuǎn)移的那塊內(nèi)存區(qū)域幾乎沒有什么內(nèi)存碎片,對(duì)象都是按順序排列在這塊內(nèi)存里的。
然后那塊被轉(zhuǎn)移的內(nèi)存區(qū)域,是不是多出來一大塊連續(xù)的可用的內(nèi)存空間?
此時(shí)就可以將新對(duì)象分配在那塊連續(xù)內(nèi)存空間里了,如下圖。

這個(gè)時(shí)候,再一次性把原來使用的那塊內(nèi)存區(qū)域中的垃圾對(duì)象全部一掃而空,全部給回收掉,空出來一塊內(nèi)存區(qū)域,如下圖。
這就是所謂的“復(fù)制算法“,把新生代內(nèi)存劃分為兩塊內(nèi)存區(qū)域,然后只使用其中一塊內(nèi)存
待那塊內(nèi)存快滿的時(shí)候,就把里面的存活對(duì)象一次性轉(zhuǎn)移到另外一塊內(nèi)存區(qū)域,保證沒有內(nèi)存碎片
接著一次性回收原來那塊內(nèi)存區(qū)域的垃圾對(duì)象,再次空出來一塊內(nèi)存區(qū)域。兩塊內(nèi)存區(qū)域就這么重復(fù)著循環(huán)使用。
5、復(fù)制算法有什么缺點(diǎn)?
復(fù)制算法的缺點(diǎn)其實(shí)非常的明顯,如果按照上述的思路,大家會(huì)發(fā)現(xiàn),假設(shè)我們給新生代1G的內(nèi)存空間,那么只有512MB的內(nèi)存空間是可以用的
另外512MB的內(nèi)存空間是一直要放在那里空著的,然后512MB內(nèi)存空間滿了,就把存活對(duì)象轉(zhuǎn)移到另外一塊512MB的內(nèi)存空間去
從始至終,就只有一半的內(nèi)存可以用,這樣的算法顯然對(duì)內(nèi)存的使用效率太低了。
6、復(fù)制算法的優(yōu)化:Eden區(qū)和Survivor區(qū)
之前我給大家分析過,系統(tǒng)運(yùn)行時(shí),對(duì)JVM內(nèi)存的使用模型,大體上就是我們的代碼不停的創(chuàng)建對(duì)象然后分配在新生代里,但是一般很快那個(gè)對(duì)象就沒人引用了,成了垃圾對(duì)象。
接著一段時(shí)間過后,新生代就滿了,此時(shí)就會(huì)回收掉那些垃圾對(duì)象,空出來內(nèi)存空間,給后續(xù)其他的對(duì)象來使用。
但是我們之前分析過,其實(shí)絕大多數(shù)的對(duì)象都是存活周期非常短的對(duì)象,可能被創(chuàng)建出來1毫秒之后就沒人引用了,他就是垃圾對(duì)象了。
所以大家可以想象一下,可能一次新生代垃圾回收過后,99%的對(duì)象其實(shí)都被垃圾回收了,就1%的對(duì)象存活了下來,可能就是一些長(zhǎng)期存活的對(duì)象,或者還沒使用完的對(duì)象。
所以實(shí)際上真正的復(fù)制算法會(huì)做出如下優(yōu)化,把新生代內(nèi)存區(qū)域劃分為三塊:
1個(gè)Eden區(qū),2個(gè)Survivor區(qū),其中Eden區(qū)占80%內(nèi)存空間,每一塊Survivor區(qū)各占10%內(nèi)存空間,比如說Eden區(qū)有800MB內(nèi)存,每一塊Survivor區(qū)就100MB內(nèi)存,如下圖。

平時(shí)可以使用的,就是Eden區(qū)和其中一塊Survivor區(qū),那么相當(dāng)于就是有900MB的內(nèi)存是可以使用的,如下圖所示。

但是剛開始對(duì)象都是分配在Eden區(qū)內(nèi)的,如果Eden區(qū)快滿了,此時(shí)就會(huì)觸發(fā)垃圾回收
此時(shí)就會(huì)把Eden區(qū)中的存活對(duì)象都一次性轉(zhuǎn)移到一塊空著的Survivor區(qū)。接著Eden區(qū)就會(huì)被清空,然后再次分配新對(duì)象到Eden區(qū)里,然后就會(huì)如上圖所示,Eden區(qū)和一塊Survivor區(qū)里是有對(duì)象的,其中Survivor區(qū)里放的是上一次Minor GC后存活的對(duì)象。
如果下次再次Eden區(qū)滿,那么再次觸發(fā)Minor GC,就會(huì)把Eden區(qū)和放著上一次Minor GC后存活對(duì)象的Survivor區(qū)內(nèi)的存活對(duì)象,轉(zhuǎn)移到另外一塊Survivor區(qū)去。
所以這里大家就能體會(huì)到,為啥是這么分配內(nèi)存空間了。
因?yàn)橹胺治隽?,每次垃圾回收可能存活下來的?duì)象就1%,所以在設(shè)計(jì)的時(shí)候就留了一塊100MB的內(nèi)存空間來存放垃圾回收后轉(zhuǎn)移過來的存活對(duì)象
比如Eden區(qū)+一塊Survivor區(qū)有900MB的內(nèi)存空間都占滿了,但是垃圾回收之后,可能就10MB的對(duì)象是存活的。
此時(shí)就把那10MB的存活對(duì)象轉(zhuǎn)移到另外一塊Survivor區(qū)域就可以,然后再一次性把Eden區(qū)和之前使用的Survivor區(qū)里的垃圾對(duì)象全部回收掉,如下圖。

接著新對(duì)象繼續(xù)分配在Eden區(qū)和另外那塊開始被使用的Survivor區(qū),然后始終保持一塊Survivor區(qū)是空著的,就這樣一直循環(huán)使用這三塊內(nèi)存區(qū)域。
這么做最大的好處,就是只有10%的內(nèi)存空間是被閑置的,90%的內(nèi)存都被使用上了
無論是垃圾回收的性能,內(nèi)存碎片的控制,還是說內(nèi)存使用的效率,都非常的好。
7、新生代垃圾回收的各種“萬一”怎么處理?
這個(gè)時(shí)候很多人看完了本文之后,一定對(duì)這里有可能發(fā)生的各種“萬一”情況有疑惑了
比如:
萬一垃圾回收過后,存活下來的對(duì)象超過了10%的內(nèi)存空間,在另外一塊Survivor區(qū)域中放不下咋整?
萬一我們突然分配了一個(gè)超級(jí)大的對(duì)象,大到啥程度?新生代找不到連續(xù)內(nèi)存空間來存放,此時(shí)咋整?
到底一個(gè)存活對(duì)象要在新生代里這么來回倒騰多少次之后才會(huì)被轉(zhuǎn)移都老年代去?
別著急,明天就會(huì)來分析這些新生代的各種“萬一”情況,以及新生代的對(duì)象是如何轉(zhuǎn)移到老年代的,然后老年代是如何觸發(fā)垃圾回收的,垃圾回收的算法又是什么樣的。
看完這兩篇文章,大家就會(huì)對(duì)新生代和老年代的對(duì)象分配、垃圾回收、對(duì)象轉(zhuǎn)移等各種原理,都非常的熟悉了。
8、昨日思考題解答
思考下面的代碼。

上述代碼下,如果垃圾回收,會(huì)回收ReplicaFetcher對(duì)象嗎?為什么?
明顯是不會(huì)的,因?yàn)镽eplicaFetcher對(duì)象被ReplicaManager對(duì)象中的實(shí)例變量引用了,然后ReplicaManager對(duì)象被Kafka類的靜態(tài)變量給引用了
所以垃圾回收的時(shí)候,是不會(huì)回收掉ReplciaFetcher對(duì)象的,否則讓存活下來的ReplicaManager對(duì)象情何以堪?
9、今日思考題
各位還記得之前教給過大家的那個(gè)系統(tǒng)對(duì)內(nèi)存使用壓力的估算方法么?
可以借助那個(gè)方法估算一下,每秒鐘系統(tǒng)會(huì)使用多少內(nèi)存空間,然后多長(zhǎng)時(shí)間會(huì)觸發(fā)一次垃圾回收?
垃圾回收之后,你們系統(tǒng)內(nèi)大體會(huì)有多少對(duì)象存活下來?為什么?
然后都有哪些對(duì)象會(huì)存活下來?存活下來的對(duì)象會(huì)占多少內(nèi)存空間?
隨著不停的跟著專欄學(xué)習(xí),希望大家多結(jié)合自己負(fù)責(zé)的系統(tǒng)來思考,你會(huì)養(yǎng)成一個(gè)核心能力,能夠從JVM的角度去考慮系統(tǒng)運(yùn)行時(shí)的模型
這樣在真正發(fā)生JVM內(nèi)存問題的時(shí)候,就能有一個(gè)非常深入的思考能力去解決問題。
End
版權(quán):公眾號(hào)儒猿技術(shù)窩
未經(jīng)許可不得傳播,如有侵權(quán)將追究法律責(zé)任