只要面試都會問到的垃圾收集算法,還不趕快收藏!!!
垃圾收集算法
1. 分代收集理論
1.1 分代假說
- 弱分代假說:絕大多數(shù)的對象存活時間很短,朝生夕滅。
- 強分代假說:熬過越多次的垃圾回收次數(shù),對象越難被消滅。
- 跨代引用假說:跨代引用相對于同代引用而言僅僅只占一小部分。
1.2 垃圾回收器設(shè)計
基于弱分代假說和強分代假說,多款常用垃圾回收器的統(tǒng)一設(shè)計原則:收集器應(yīng)該將Java堆劃分不同的區(qū)域,根據(jù)對象年齡分配待不同的區(qū)域中存。
在Java堆內(nèi)存中,分兩部分:
- 新生代:這部分區(qū)域中的對象存活時間都很短,基本遇到垃圾回收就會被清除
- 老年代:這部分區(qū)域中的對象存活時間很久了,熬過多次垃圾回收 ,年齡很大,很難被清除。
對于新生代的垃圾回收,垃圾收集器并不需要去關(guān)注回收的對象,只需要關(guān)注存活下來的對象。 每次回收后存活的少量對象,將逐步升級到老年代當(dāng)中。
對于老年代的垃圾回收,虛擬機(jī)可以用較低的頻率回收這個區(qū)域。這樣子就同時兼顧了垃圾回收的時間開銷和內(nèi)存空間的有效利用。
將Java堆分成新生代和老年代分別使用不同的策略垃圾回收之后,出現(xiàn)一個問題:跨代引用。存在相互引用的兩個對象,應(yīng)該是同時生存同時消亡的。如果新生代與老年代的對象存在跨代引用,那么由于老年代的關(guān)系所以這兩個對象很難被回收。當(dāng)新生代對象到達(dá)足夠年齡之后,將進(jìn)入老年代,此時的跨代引用就消除了。
很好的辦法,等就對了。當(dāng)然不是!那樣子老年代就會越來越臃。但是我們又不想為了一個跨代引用對整個老年代進(jìn)行掃描,那么就通過一個叫記憶集的結(jié)構(gòu)來解決。
記憶集將老年代劃分為若干個小塊,標(biāo)記了老年代的哪一塊存在跨代引用。當(dāng)新生代進(jìn)行垃圾回收的時候,通過記憶集將包含了跨代引用的那一小部分老年代也會進(jìn)行垃圾回收。
1.3垃圾回收
- 新生代收集 Minor GC / Young GC:指目標(biāo)指示新生代的垃圾收集
- 老年代收集 Major GC / Old GC:指目標(biāo)指示老年代的垃圾收集
- 混合收集 Mixed GC:整個新生代和部分老年代的垃圾收集
- 整堆收集 Full GC:收集整個 Java 堆和方法區(qū)的垃圾收集
2. 標(biāo)記-清除算法
2.1 介紹
算法分為“標(biāo)記”和“清除”兩個階段:首先標(biāo)記出所有需要回 收的對象,在標(biāo)記完成后,統(tǒng)一回收掉所有被標(biāo)記的對象,也可以反過來,標(biāo)記存活的對象,統(tǒng)一回 收所有未被標(biāo)記的對象。
2.2 缺點
- 執(zhí)行效率不穩(wěn)定,效率隨著對象數(shù)量的增加而降低
- 內(nèi)存碎片問題,清除之后出現(xiàn)不連續(xù)的內(nèi)存碎片,當(dāng)需要為大對象分配足夠的連續(xù)內(nèi)存的時候,需要再次GC
3. 標(biāo)記-復(fù)制算法
3.1. 介紹
它將可用 內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當(dāng)這一塊的內(nèi)存用完了,就將還存活著 的對象復(fù)制到另外一塊上面,然后再把已使用過的內(nèi)存空間一次清理掉。
3.2 缺點
- 如果存活對象過多,可能會有大量的時間浪費復(fù)制上,所以這方法主要是針對存活率小的情況。
- 空間浪費,,一般的空間作為保留區(qū)域。
3.3 作用
這種收集算法多用于回收新生代。
因為新生代中有98%的對象都熬不過第一輪的回收,只有2%的對象可以存活下來。如果盲目將新生代劃分為1 : 1的比例,就會浪費很多的空間。所以廠商們將新生代的布局劃分為了一塊較大的 Eden 空間和兩塊較小的 Survivor 空間。每次分配內(nèi)存都是只是用 Eden 和其中一塊 Survivor 空間。當(dāng) Minor 垃圾回收后,將存活的對象存放在保留的 Survivor 空間中,清空 Eden 和之前使用的 Survivor 空間。
HotSpot 虛擬機(jī)默認(rèn) Eden 和 Survivor 的大小比例是 8 : 1,也就是每次新生代中可用的空間為 90%,只有一個 Survivor 會被浪費。當(dāng)遇到一個 Survivor 空間不足以容納一次 Minor GC 后存活的對象時,就需要依賴?yán)夏甏M(jìn)行分配擔(dān)保。新生代的分配擔(dān)保就是指空間不夠放下存活對象,就會將這些對象通過分配擔(dān)保機(jī)制直接進(jìn)入老年代。
4. 標(biāo)記-整理算法
4.1 介紹
標(biāo)記過程仍然與“標(biāo)記-清除”算法一樣,但后續(xù)步驟不是直接對可回收對象進(jìn)行清理,而是讓所有存活的對象都向內(nèi)存空間一端移動,然后直接清理掉邊界以外的內(nèi)存
5. 總結(jié)
在 GC 過程中移動存活對象,并更新所有引用這些對象的地方的數(shù)據(jù),是一種極為負(fù)重的操作,而且移動操作必須暫停用戶應(yīng)用進(jìn)程才能進(jìn)行。這種暫停被描述為“ Stop The World”,也就是 STW。
如果像標(biāo)記-清除算法那樣子完全不考慮移動和整理,Java 堆中的空間碎片問題將十分嚴(yán)重,只能依賴更復(fù)雜的內(nèi)存分配器和內(nèi)存訪問器來解決。內(nèi)存訪問是用戶程序中最頻繁的操作,如果在此環(huán)節(jié)上添加額外的負(fù)擔(dān),勢必會直接影響程序的吞吐量。
那么移動會發(fā)生 STW,但是內(nèi)存分配的時候更簡單。直接清除會產(chǎn)生內(nèi)存碎片,但是垃圾回收的時候更方便。無論是移動與否都有弊端。從垃圾回收的 STW 來看,直接清除的 STW 最短,甚至不用停頓,但從整個程序的吞吐量來看,移動對象更劃算。不同的虛擬機(jī)實現(xiàn)廠商的注重點不同,他們的收集器也不一樣。
還有一種“和稀泥式”解決方案可以不在內(nèi)存分配和訪問上增加太大額外負(fù)擔(dān),做法是讓虛擬機(jī)平時多數(shù)時間都采用標(biāo)記-清除算法,暫時容忍內(nèi)存碎片的存在,直到內(nèi)存空間的碎片化程度已經(jīng)大到影響對象分配時,再采用標(biāo)記-整理算法收集一次,以獲得規(guī)整的內(nèi)存空間。前面提到的基于標(biāo)記-清除算法的 CMS 收集器面臨空間碎片過多時采用的就是這種處理辦法。