深入理解Java虛擬機(五):內存回收方法論——垃圾收集算法

引言

從如何判定對象消亡的角度出發,垃圾收集算法可以劃分爲“引用計數式垃圾收集”(Reference Counting GC)和“追蹤式垃圾收集”(Tracing GC)兩大類,這兩類也常被稱作“直接垃圾收集”和“間接垃圾收集”。下面總結的算法均屬於追蹤式垃圾收集的範疇。

名詞解釋

  • 部分收集(Partial GC):指目標不是完整收集整個Java堆的垃圾收集,其中又分爲:

    1)新生代收集(Minor GC/Young GC):指目標只是新生代的垃圾收集。

    2)老年代收集(Major GC/Old GC):指目標只是老年代的垃圾收集。目前只有CMS收集器會有單獨收集老年代的行爲。

    3)混合收集(Mixed GC):指目標是收集整個新生代以及部分老年代的垃圾收集。目前只有G1收集器會有這種行爲。

  • 整堆收集(Full GC):收集整個Java堆和方法區的垃圾收集。

收集算法

標記-清除算法

1)兩個階段:

首先標記出所有需要回收的對象,在標記完成後,統一回收掉所有被標記的對象,也可以反過來,標記存活的對象,統一回收所有未被標記的對象。

2)兩個缺點:

第一個是執行效率不穩定,如果Java堆中包含大量對象,而且其中大部分是需要被回收的,這時必須進行大量標記和清除的動作,導致標記和清除兩個過程的執行效率都隨對象數量增長而降低。

第二個是內存空間的碎片化問題,標記、清除之後會產生大量不連續的內存碎片,空間碎片太多可能會導致當以後在程序運行過程中需要分配較大對象時無法找到足夠的連續內存而不得不提前觸發另一次垃圾收集動作。

3)算法示意圖:

在這裏插入圖片描述
標記-複製算法

1)提出目的:

標記-複製算法常被簡稱爲複製算法。爲了解決標記-清除算法面對大量可回收對象時執行效率低的問題。

2)算法方案:

它將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活着的對象複製到另外一塊上面,然後再把已使用過的內存空間一次清理掉。

3)兩種情況:

第一種:如果內存中多數對象都是存活的,這種算法將會產生大量的內存間複製的開銷。

第二種:如果多數對象都是可回收的情況,算法需要複製的就是佔少數的存活對象,而且每次都是針對整個半區進行內存回收,分配內存時也就不用考慮有空間碎片的複雜情況,只要移動堆頂指針,按順序分配即可。

4)優缺點:

實現簡單,運行高效。這種複製回收算法的代價是將可用內存縮小爲了原來的一半,空間浪費太多。現在的商用Java虛擬機大多都優先採用了這種收集算法去回收新生代。

5)算法示意圖:
在這裏插入圖片描述
半區複製分代策略

1)提出背景:

針對新生代“朝生夕滅”的特點,Andrew Appel提出了一種更優化的半區複製分代策略,現在稱爲“Appel式回收”。

HotSpot虛擬機的Serial、ParNew等新生代收集器均採用了這種策略來設計新生代的內存佈局。

2)算法方案:

把新生代分爲一塊較大的Eden空間和兩塊較小的Survivor空間,每次分配內存只使用Eden和其中一塊Survivor。

發生垃圾蒐集時,將Eden和Survivor中仍然存活的對象一次性複製到另外一塊Survivor空間上,然後直接清理掉Eden和已用過的那塊Survivor空間。

HotSpot虛擬機默認Eden和Survivor的大小比例是8∶1,也即每次新生代中可用內存空間爲整個新生代容量的90%(Eden的80%加上一個Survivor的10%),只有一個Survivor空間,即10%的新生代是會被“浪費”的。

3)分配擔保:

當然,任何人都沒有辦法百分百保證每次回收都只有不多於10%的對象存活,因此Appel式回收還有一個充當罕見情況的“逃生門”的安全設計,當Survivor空間不足以容納一次Minor GC之後存活的對象時,就需要依賴其他內存區域(實際上大多就是老年代)進行分配擔保(Handle Promotion)。

內存中的分配擔保也就是說,如果另外一塊Survivor空間沒有足夠空間存放上一次新生代收集下來的存活對象,這些對象便將通過分配擔保機制直接進入老年代,這對虛擬機來說就是安全的。

標記-整理算法

1)提出背景:

標記-複製算法在對象存活率較高時就要進行較多的複製操作,效率將會降低。更關鍵的是,如果不想浪費50%的空間,就需要有額外的空間進行分配擔保,以應對被使用的內存中所有對象都100%存活的極端情況,所以在老年代一般不能直接選用這種算法。

2)算法方案:

針對老年代對象的存亡特徵,1974年Edward Lueders提出了另外一種有針對性的“標記-整理”(Mark-Compact)算法,其中的標記過程仍然與“標記-清除”算法一樣,但後續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向內存空間一端移動,然後直接清理掉邊界以外的內存。

3)算法示意圖:
在這裏插入圖片描述
4)算法對比:

標記-清除算法與標記-整理算法的本質差異在於前者是一種非移動式的回收算法,而後者是移動式的。

優缺點:

第一種:如果移動存活對象,尤其是在老年代這種每次回收都有大量對象存活區域,移動存活對象並更新所有引用這些對象的地方將會是一種極爲負重的操作,而且這種對象移動操作必須全程暫停用戶應用程序才能進行,稱之爲“Stop The World”.

第二種:如果跟標記-清除算法那樣完全不考慮移動和整理存活對象的話,彌散於堆中的存活對象導致的空間碎片化問題就只能依賴更爲複雜的內存分配器和內存訪問器來解決。內存的訪問是用戶程序最頻繁的操作,假如在這個環節上增加了額外的負擔,勢必會直接影響應用程序的吞吐量。

基於以上兩點,是否移動對象都存在弊端,移動則內存回收時會更復雜,不移動則內存分配時會更復雜。從垃圾收集的停頓時間來看,不移動對象停頓時間會更短,甚至可以不需要停頓,但是從整個程序的吞吐量來看,移動對象會更划算。

HotSpot虛擬機裏面關注吞吐量的Parallel Scavenge收集器是基於標記-整理算法的,而關注延遲的CMS收集器則是基於標記-清除算法的。

最後,還有一種兩者結合式的方案,做法是讓虛擬機平時多數時間都採用標記-清除算法,暫時容忍內存碎片的存在,直到內存空間的碎片化程度已經大到影響對象分配時,再採用標記-整理算法收集一次,以獲得規整的內存空間。基於標記-清除算法的CMS收集器面臨空間碎片過多時採用的就是這種處理辦法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章