背景:看完《深入理解Java虛擬機(jī)》和相關(guān)博客,對(duì)JVM還是沒(méi)有一個(gè)條理清晰的認(rèn)識(shí),遂提取了書(shū)中相關(guān)知識(shí)點(diǎn)和參考相關(guān)優(yōu)秀博客并整理成JVM專(zhuān)題博文系列,幫助自己鞏固并理清有關(guān)JVM的知識(shí)重點(diǎn),也分享出來(lái)給有需要的童鞋,如有差錯(cuò),歡迎拍磚!
標(biāo)記-清除算法
最基礎(chǔ)的算法,分標(biāo)記和清除兩個(gè)階段:首先標(biāo)記處所需要回收的對(duì)象,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對(duì)象。
它有兩點(diǎn)不足:
一個(gè)效率問(wèn)題,標(biāo)記和清除過(guò)程都效率不高;
一個(gè)是空間問(wèn)題,標(biāo)記清除之后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片(類(lèi)似于我們電腦的磁盤(pán)碎片),空間碎片太多導(dǎo)致需要分配大對(duì)象時(shí)無(wú)法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾回收動(dòng)作。

復(fù)制算法(標(biāo)記-復(fù)制-清除算法)
為了解決效率問(wèn)題,出現(xiàn)了“復(fù)制”算法,他將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只需要使用其中一塊。當(dāng)一塊內(nèi)存用完了,將還存活的對(duì)象復(fù)制到另一塊上面(移動(dòng)堆頂指針,按順序分配內(nèi)存),然后再把剛剛用完的內(nèi)存空間一次清理掉。這樣就解決了內(nèi)存碎片問(wèn)題,但是代價(jià)就是可以用內(nèi)容就縮小為原來(lái)的一半。
目前此種算法主要用于新生代回收(圖中有標(biāo)注)。
因?yàn)樾律闹?8%的對(duì)象都是很快就需要被回收的對(duì)象,這一點(diǎn)大家在編程時(shí)可以體會(huì)到,所以并不需要1:1的比例來(lái)劃分內(nèi)存空間,在新生代中JVM是按照“8:1:1”的比例(圖中有標(biāo)注)來(lái)將整個(gè)新生代內(nèi)存劃分為一塊較大的Eden區(qū)和兩塊較小的Survivor區(qū)(S0、S1)。
每次使用Eden區(qū)和其中一個(gè)Survivor區(qū),當(dāng)發(fā)生回收時(shí)將Eden區(qū)和Survivor區(qū)中還存活的對(duì)象一次性復(fù)制到另一塊Survivor區(qū)上,最后清理掉Eden區(qū)和剛才使用過(guò)的Survivor區(qū)。理想情況下,每次新生代中的可用空間是整個(gè)新生代容量的90%(80%+10%),只會(huì)有10%的內(nèi)存會(huì)被浪費(fèi)。實(shí)際情況中,如果另外一個(gè)10%的Survivor區(qū)無(wú)法裝下所有還存活的對(duì)象時(shí),就會(huì)將這些對(duì)象直接放入老年代空間中(這塊在后面的分代回收算法會(huì)說(shuō)到,這里先了解下)。

標(biāo)記-整理算法(標(biāo)記-清除-壓縮算法)
復(fù)制算法在對(duì)象存活率較高時(shí)就會(huì)進(jìn)行頻繁的復(fù)制操作,效率將降低,而且如果不想浪費(fèi)50%的內(nèi)存空間的話(huà),就還需要額外的空間進(jìn)行分配擔(dān)保,以應(yīng)對(duì)存活對(duì)象超額的情況。顯然老年代不能采用2)中的復(fù)制算法。
因此又有了標(biāo)記-整理算法,標(biāo)記過(guò)程同標(biāo)記-清除算法,但是在后續(xù)步驟不是直接對(duì)對(duì)象進(jìn)行清理,而是讓所有存活的對(duì)象都向一側(cè)移動(dòng),然后直接清理掉端邊界以外的內(nèi)存。

分代收集算法
當(dāng)前商業(yè)虛擬機(jī)的GC都是采用分代收集算法,這種算法并沒(méi)有什么新的思想,而是根據(jù)對(duì)象存活周期的不同將堆分為:新生代和老年代,方法區(qū)稱(chēng)為永久代(在新的版本中已經(jīng)將永久代廢棄,引入了元空間的概念,永久代使用的是JVM內(nèi)存而元空間直接使用物理內(nèi)存),這樣就可以根據(jù)各個(gè)年代的特點(diǎn),采用合適的收集算法了。

新生代中的對(duì)象“朝生夕死”,每次GC時(shí)都會(huì)有大量對(duì)象死去,少量存活,使用復(fù)制算法。新生代又分為Eden區(qū)和Survivor區(qū)(Survivor from、Survivor to),大小比例默認(rèn)為8:1:1。
老年代中的對(duì)象因?yàn)閷?duì)象存活率高、沒(méi)有額外空間進(jìn)行分配擔(dān)保,就使用標(biāo)記-清除或標(biāo)記-整理算法。
回收過(guò)程
新產(chǎn)生的對(duì)象優(yōu)先進(jìn)去Eden區(qū),當(dāng)Eden區(qū)滿(mǎn)了之后再使用Survivor from,當(dāng)Survivor from 也滿(mǎn)了之后就進(jìn)行Minor GC(新生代GC),將Eden和Survivor from中存活的對(duì)象copy進(jìn)入Survivor to,然后清空Eden和Survivor from,這個(gè)時(shí)候原來(lái)的Survivor from成了新的Survivor to,原來(lái)的Survivor to成了新的Survivor from。復(fù)制的時(shí)候,如果Survivor to 無(wú)法容納全部存活的對(duì)象,則根據(jù)老年代的分配擔(dān)保(類(lèi)似于銀行的貸款擔(dān)保)將對(duì)象copy進(jìn)去老年代,如果老年代也無(wú)法容納,則進(jìn)行Full GC(老年代GC)。
大對(duì)象直接進(jìn)入老年代,JVM中有個(gè)參數(shù)配置-XX:PretenureSizeThreshold,令大于這個(gè)設(shè)置值的對(duì)象直接進(jìn)入老年代,目的是為了避免在Eden和Survivor區(qū)之間發(fā)生大量的內(nèi)存復(fù)制。
長(zhǎng)期存活的對(duì)象進(jìn)入老年代,JVM給每個(gè)對(duì)象定義一個(gè)對(duì)象年齡計(jì)數(shù)器,如果對(duì)象在Eden出生并經(jīng)過(guò)第一次Minor GC后仍然存活,并且能被Survivor容納,將被移入Survivor并且年齡設(shè)定為1。沒(méi)熬過(guò)一次Minor GC,年齡就加1,當(dāng)他的年齡到一定程度(默認(rèn)為15歲,可以通過(guò)XX:MaxTenuringThreshold來(lái)設(shè)定),就會(huì)移入老年代。但是JVM并不是永遠(yuǎn)要求年齡必須達(dá)到最大年齡才會(huì)晉升老年代,如果Survivor 空間中相同年齡(如年齡為x)所有對(duì)象大小的總和大于Survivor的一半,年齡大于等于x的所有對(duì)象直接進(jìn)入老年代,無(wú)需等到最大年齡要求。
參考
面試必問(wèn)之JVM原理
一張圖看懂JVM之垃圾回收算法詳解
技術(shù)討論 & 疑問(wèn)建議 & 個(gè)人博客
版權(quán)聲明: 本博客所有文章除特別聲明外,均采用 CC BY-NC-SA 3.0 許可協(xié)議,轉(zhuǎn)載請(qǐng)注明出處!