JVM垃圾收集演算法

分代收集理論

垃圾收集區域

 部分收集(Partial GC):
       新生代收集(Minor GC/Young GC):
       老年代收集(Major GC/Old GC):
       混合收集(Mixed GC):
 整堆收集(Full GC):收集整個Java堆和方法區的垃圾收集

標記-清除演算法

       最早出現也是最基礎的垃圾收集演算法。演算法分為"標記" 和 "清除" 兩個階段: 首先標記初所有需要回收的對象,在標記完成後,統一回收掉所有被標記的對象,也可以反過來,標記存活的對象,統一回收所有未被標記的對象。標記的過程就是對象是否屬於垃圾的判定過程。

主要缺點

執行效率不穩定

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

記憶體空間碎片化問題

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

标记-清除算法示意图

標記-複製演算法

目的:解決前者執行效率低的問題

       為了解決 標記-清除 演算法面對大量可回收對象時執行效率低的問題。1969年 Fenichel 提出一種稱為"半區複製"(Semispace Copying)的垃圾收集演算法,它將可用記憶體按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的記憶體用完了,就將還存活的對象複製
 到另一塊上面,然後再把已使用過的記憶體空間一次性清理掉。

缺點:記憶體縮小一半,空間浪費

優化後 Appel 式回收

       HotSpot虛擬機的Serial、ParNew 等新生代收集器均採用這種策略來設計新生代的記憶體布局。
       Appel式回收的具體做法式把新生代分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次分配記憶體只使用Eden和其中一塊Survivor。發送垃圾收集時,將Eden和Survivor中仍然存活的對象一次性複製到另一塊Surivor空間上,然後直接清理掉Eden和已用過的那塊Survivor
 空間。HotSpot虛擬機默認Eden和Survivor的大小比例時8:1:1。當Survivor空間不足以容納一次Minor GC之後存貨的對象時,就需要依賴其他記憶體區域進行分配擔保(Handle Promotion)

標記複製演算法示意圖

分配擔保

       在發生Minor GC之前,虛擬機必須先檢查老年代最大可用的連續空間是否大於新生代所有對象總空間,如果這個條件成立,那這一次 Minor GC可以確保是安全的。如果不成立,則虛擬機會檢查看 -XX:HandlePromotionFailure 參數的設置值是否允許擔保失敗(Handle 
 Promotion Failure);如果允許,那會繼續檢查老年代最大可以用的連續空間是否大於歷次晉陞到老年代對象的平均大小,如果大於,將嘗試一次Minor GC,儘管這次GC是有風險的;如果小於,或者參數設置false,哪就要進行一次Full GC。

分配担保视图

標記-整理演算法

       應對被使用的記憶體中所有對象都100%存活的極端情況,老年代一般採用 "標記-整理"(Mark - Compact)演算法,先標記,然後讓所有存活對象都向記憶體空間一端移動,然後直接清理掉邊界以外的記憶體。
       標記-清除演算法與標記-整理演算法的本質差異在於前者是一種非移動式的回收演算法,後者是移動式的。

是否移動回收後存活的對象是一項優缺點並存的風險決策

如果移動存活對象

       如果移動存活對象,尤其是在老年代這種每次回收都有大量對象存活區域,移動存活對象並更新所有引用這些對象的地方將是一種極為負重的操作,並且這種對象移動操作必須全程暫停用戶應用程式才能進行。

如果不移動和整理存活對象

       如果完全不考慮移動和整理存活對象的話,彌散與堆中的存活對象導致的空間碎片化問題就只能依賴更為複雜的記憶體分配器和記憶體訪問器來解決。譬如通過"分區空閑分配鏈表"來解決記憶體分配問題。會額外給記憶體增加負擔,影響應用程式的吞吐量。

標記-整理演算法

Tags: