從如何判定對象消亡的角度出發(fā),垃圾收集算法可以劃分為“引用計數(shù)式垃圾收集”和“追蹤式垃圾收集”兩大類,這兩類也被稱為“直接垃圾收集”和“間接垃圾收集”沐祷。
1.分代收集理論
兩個分代假說:
(1)弱分代假說:絕大多數(shù)對象都是朝生夕滅的
(2)強分代假說:熬過越多次垃圾收集過程的對象就越難以消亡
奠定了垃圾收集器的一致設(shè)計原則:
收集器應(yīng)該將java堆劃分出不同的區(qū)域,然后將回收對象依據(jù)其年齡分配到不同的區(qū)域之中存儲。
把java堆劃分為新生代摩窃,老年代兩個區(qū)域。在新生代中每次垃圾收集時都發(fā)現(xiàn)有大批對象死去芬骄,而每次回收后存活的少量對象猾愿,將會逐步晉升到老年代中存放。
分代收集并非只是簡單劃分一下內(nèi)存區(qū)域那么容易账阻,它至少存在一個明顯的困難:對象不是孤立的蒂秘,對象之間會存在跨代引用。假如要現(xiàn)在進行一次只局限于新生代區(qū)域內(nèi)的收集淘太,但新生代中的對象完全有可能被老年代所引用姻僧,為了找出該區(qū)域的存活對象观挎,不得不在固定的GCRoots之外,再額外遍歷整個老年代中所有對象來確倍位可達(dá)性分析結(jié)果的正確性,反過來也是一樣造成。為了解決這個問題显熏,就需要對分代收集理論添加第三條經(jīng)驗法則:
(3)跨代引用假說:跨代引用相對于同代引用來說僅占極少數(shù)。
即存在相互引用關(guān)系的兩個對象是應(yīng)該傾向于同時生存或者同時消亡的晒屎。比如:如果某個新生代對象存在跨代引用喘蟆,由于老年代對象難以消亡,該引用會使得新生代對象在收集時同樣得以存活鼓鲁,進而隨著年齡增長之后晉升到老年代中蕴轨,這是跨代引用也隨機被消除了。
依據(jù)這條假說骇吭,我們只需在新生代上建立一個全局的數(shù)據(jù)結(jié)構(gòu)橙弱,把老年代劃分為若干小塊,標(biāo)識出老年代的哪一塊內(nèi)存會存在跨代引用燥狰。此后當(dāng)發(fā)生新生代區(qū)域收集時棘脐,只有包含了跨代引用的小塊內(nèi)存里的對象才會被加入到GC Roots進行掃描。
2.標(biāo)記-清除算法
算法分為“標(biāo)記”和“清除”兩個階段:首先標(biāo)記出所有需要回收的對象龙致,在標(biāo)記完成后蛀缝,統(tǒng)一回收掉所有被標(biāo)記的對象,也可以反過來目代,標(biāo)記存活的對象屈梁,回收所有未被標(biāo)記的對象。標(biāo)記過程就是對象是否處于垃圾的判定過程榛了。
標(biāo)記-清除算法缺點:
1.執(zhí)行效率不穩(wěn)定在讶,如果java堆中包含大量對象,而且其中大部分是需要被回收的忽冻,這時必須進行大量標(biāo)記和清除動作真朗,導(dǎo)致標(biāo)記和清除兩個過程的執(zhí)行效率都隨著對象數(shù)量增長而降低;
2.內(nèi)存空間碎片化問題僧诚,標(biāo)記遮婶,清除之后會產(chǎn)生大量不連續(xù)的內(nèi)存碎片,空間碎片太多可能會導(dǎo)致以后在程序運行過程中需要分配較大對象時無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動作湖笨。
3.標(biāo)記-復(fù)制算法
為了解決標(biāo)記-清除算法面對大量可回收對象時執(zhí)行效率低的問題旗扑,F(xiàn)enichel提出了一種稱為半?yún)^(qū)復(fù)制垃圾收集算法,它將可用內(nèi)存容量劃分為大小相等的兩塊慈省,每次只使用其中的一塊臀防。當(dāng)這一塊內(nèi)存用完了,就將還存活著的對象復(fù)制到另外一塊上面,然后再把已使用過的內(nèi)存空間一次性清理掉袱衷。如果內(nèi)存中多數(shù)對象都是存活的捎废,這種算法將會產(chǎn)生大量的內(nèi)存空間開銷,但對于多數(shù)對象都是可回收的情況致燥,算法需要復(fù)制的就是占少數(shù)的存活對象登疗,而且每次都是針對整個半?yún)^(qū)進行內(nèi)存回收,分配內(nèi)存時就不用了考慮空間碎片的復(fù)雜情況嫌蚤,只要移動堆頂指針辐益,按順序分配即可。
缺點:
這種復(fù)制回收算法的代價是將可用內(nèi)存縮小為原來的一半脱吱,空間浪費太多智政。
Andrew Appel針對具備“朝生夕滅”特點的對象,提出了一種更優(yōu)化的半?yún)^(qū)復(fù)制分代策略箱蝠,現(xiàn)在稱為“Appel式回收”续捂。
具體做法:把新生代分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次分配只使用Eden和其中一塊Survivor宦搬。發(fā)生垃圾收集時疾忍,將Eden和Survivor中仍然存活的對象一次性復(fù)制到另外一塊Survivor空間上,然后直接清理掉Eden和已經(jīng)用過的那塊Survivor空間床三。Eden和Survivor的大小比例是8:1一罩,每次新生代中可用內(nèi)存空間為整個新生代容量的90%,只有一個Survivor空間是被“浪費的”撇簿。任何人都沒有辦法保證每次回收只有不多于10%的對象存活聂渊,當(dāng)Survivor空間不足以容納一次MinorGC之后的存活的對象時,就需要依賴其他內(nèi)存區(qū)域進行分配擔(dān)保四瘫。如果另一塊Survivor空間沒有足夠空間存放上一次新生代收集下來的存活對象汉嗽,這些對象便將通過分配擔(dān)保機制直接進入老年代,這對虛擬機來說就是安全的找蜜。
4.標(biāo)記-整理算法
標(biāo)記-復(fù)制算法在對象存活率較高時就要進行較多的復(fù)制操作饼暑,效率將會降低。如果不想浪費50%的空間洗做,就需要額外的空間進行復(fù)制擔(dān)保弓叛,以應(yīng)對內(nèi)存中所有對象都100%存活的極端情況,所以老年代一般不能直接選用這種算法诚纸。
標(biāo)記過程仍然與“標(biāo)記-清除”算法一樣撰筷,讓所有存活的對象都向內(nèi)存空間一端移動,然后直接清理掉邊界以外的內(nèi)存畦徘。標(biāo)記清除算法與標(biāo)記整理算法的本質(zhì)差異在于前者是一種非移動式的回收算法毕籽,而后者是移動式的抬闯。
如果移動存活對象,尤其是在老年代這種每次回收都有大量對象存活區(qū)域关筒,移動存活對象并更新所有引用這些對象的地方將會是一種極為負(fù)重的操作溶握,而且這種對象移動操作必須全程暫停用戶應(yīng)用程序才能進行。
但如果跟標(biāo)記清除算法那樣完全不考慮移動和整理存活對象的話蒸播,彌散于堆中的存活對象導(dǎo)致的空間碎片化問題就只能依賴于更為復(fù)雜的內(nèi)存分配器和內(nèi)存訪問器來解決奈虾。譬如通過“分區(qū)空閑分配鏈表”來解決內(nèi)存分配問題。
基于以上兩點廉赔,是否移動對象都存在弊端,移動則內(nèi)存回收時會更復(fù)雜匾鸥,不移動則內(nèi)存分配時會更復(fù)雜蜡塌。從垃圾收集的停頓時間來看,不移動對象停頓時間更短勿负,甚至不需要停頓馏艾,但從整個程序的吞吐量來看,移動對象會更劃算奴愉。
還有一種做法“和稀泥式”解決方案可以不在內(nèi)存分配和訪問上增加太大額外負(fù)擔(dān)琅摩。做法是讓虛擬機平時大多數(shù)時間都采用標(biāo)記-清除算法,暫時容忍內(nèi)存碎片的存在锭硼,直到內(nèi)存空間的碎片化程度已經(jīng)達(dá)到影響對象分配時房资,再采用標(biāo)記-整理算法收集一次,以獲得規(guī)整的內(nèi)存空間檀头。