JVM05-垃圾收集算法

前言

上一篇我們介紹了JVM04-JVM中內存溢出以及其處理方法。這一篇文章我們來熟悉下JVM中各種垃圾回收算法。這些垃圾收集算法是後面各種垃圾收集器的算法基礎。閒話少敘,讓我們直入主題。

標記-清除算法

標記-清除算法分爲"標記"和"清除"兩個階段,首先標記所有需要回收的對象,在標記完成後,統一回收掉所有被標記的對象,也可以反過來,標記存活的對象,統一回收未標記的對象,標記的過程就是對象是否屬於垃圾的判定過程,一般是通過可達性分析算法,也就是說某個對象到GC Root間是否有引用鏈相連,如果沒有則判斷該對象不再被使用,也就是說是可以被回收的。如下圖所示,標黃的內存塊,在標記之後,就被清除了,留下來不少的內存碎片。
在這裏插入圖片描述

標記-清除算法的優點

  1. 實現簡單
    標記-清除算法實現簡單, 與其他算法的組合也相應地簡單。
  2. 與保守式GC算法兼容中,對象是不能被移動的,因此保守式GC算法跟把對象從現在的場所複製算法與標記-壓縮算法不兼容。標記-清除算法因爲不會移動對象,所以非常適合搭配保守式GC算法。事實上,在很多采用保守式GC算法的處理程序中也用到了標記-清除算法。

標記-清除算法的缺點

  1. 分配速度
    執行效率不穩定,如果Java堆中包含大量對象,而且這些對象大部分是需要回收的,這是必須進行大量標記和清除動作,導致標記和清除兩個過程的執行效率都隨着對象數量增長而降低。
  2. 碎片化
    內存空間的碎片問題、標記、清除之後會產生大量不連續的內存碎片,空間碎片太多會導致當以後在程序運行過程中需要分配較大對象時無法找到足夠連續內存而不得不提前觸發另一次垃圾收集動作。

標記-複製算法

標記-複製算法將可用的內存容量劃分爲大小相等的兩塊,每次只使用其中的一塊,當這一塊的內存用完了,就將還存活着的對象複製到另外一塊上面,然後再把已使用的內存空間一次清理掉。如果內存中多數對象都是存活的,這種算法將會產生大量的內存間複製的開銷,但對於多數對象都是可回收的情況下,算法需要複製的就是佔少數的存活對象。而且每次都是針對整個半區進行內存回收,分配內存時就不用考慮有內存碎片的複雜情況了。如下圖所示:回收之後將存活的對象全部移動到原來的保留區域。
在這裏插入圖片描述

標記-複製算法的優點

  1. 優秀的吞吐量
    標記-清除算法消耗的吞吐量是搜索活動對象(標記階段)所花費的時間和搜索整體堆(清除階段)所花費的時間之和。
    另一方面,因爲標記-複製算法只搜索並複製活動對象,所以跟一般的標記-清除算法相比,它能在較短時間內完成GC,也就是說,其吞吐量優秀。
    尤其是堆越大,差距越明顯。
  2. 可實現高速分配
    標記-複製算法不使用空閒鏈表,這是因爲分塊是一塊連續的內存空間,比起標記-清除算法等使用空閒鏈表的分配,標記-複製算法明顯快得多。
  3. 不會發生碎片化
    存活對象被幾種安排到保留區域,像這樣把對象重新集中,放在堆的一端的行爲就叫作壓縮,在標記-複製算法中,每次運行GC時都會執行壓縮。因此複製算法不會發生碎片化。

標記-複製算法的缺點

  1. 堆使用效率低下
    標記-複製算法把堆二等分,通常只能利用其中的一半來安排對象,也就是說,只有一半的堆能被使用,相比其他能使用整個堆的GC算法而言,這是標記-複製算法的一個重大缺陷。
  2. 不兼容保守式GC算法
    標記-複製算法因爲必須要移動對象重寫指針,所以跟保守式GC算法不相容。

Appel式回收

在1989年,Andrew Appel針對具備"朝生夕滅"特點的對象,提出了一種更優化的半區複製分代策略,稱之爲"Appel式回收"。Appel式回收的具體做法就是把新生代分爲一塊較大的Eden和Survivor中仍然存活的對象一次性複製到另一塊Survivor空間上,然後直接清理掉Eden和已用過的那塊Survivor空間,HotSpot虛擬機 默認Eden和Survivor的大小比例是8:1,也即每次新生代可用空間爲整個新生代容量的90%(Eden的80%加上一個Survivor的10%),只有一個Survivor空間,即10%的新生代會被"浪費"掉。

標記-整理算法

標記-複製算法在對象存活率較高時就要進行較多的複製操作,效率將會降低。更關鍵的是,如果不想浪費50%的空間,就需要有額外的空間進行分配擔保,以應對被使用的內存中所有對象都100%存活的極端情況。所以老年代一般不能直接選用這種算法。所以,針對老年代的垃圾收集,有標記-整理算法,首先還是標記所有需要回收的對象,然後讓所有存活的對象都想內存一端移動,然後,直接清理掉邊界以外的內存。
如果移動存活對象,尤其是在老年代這種每次回收都有大量對象存活區域,移動存活對象並更新所有引用這些對象的地方將會是一種極爲負重的操作。而且這種對象移動操作必須全程暫停用戶應用程序才能進行。移動對象則內存回收時會更復雜,不移動則內存分配時會更復雜,從垃圾收集的停頓時間來看,不移動對象停頓時間會更短,甚至不需要停頓,但是從整個程序的吞吐量來看,移動對象會更划算。關注吞吐量的Parallel Scavenge收集器是基於標記-整理算法的,而關注延遲的CMS收集器則是基於標記-清除算法的。標記-整理算法清理過程如下圖所示:
在這裏插入圖片描述

標記-整理算法的優點

  1. 標記-整理算法會執行壓縮,和其他算法相比而言,堆利用效率高。而且標記-整理算法不會出現標記-複製算法那樣只能利用半個堆的情況。另外,由於有了壓縮過程,不會產生碎片化。

標記-整理算法的缺點

  1. 壓縮花費計算成本
    標記-清除算法中,清除階段也要搜索整個堆,不過搜索1次就夠了,但標記-壓縮算法要搜索3次,這樣就要花費約3倍的時間,這是一個相當巨大的缺陷,特別是堆越大,所消耗的成本也就越大。

保守式GC算法

前面提到了保守式,簡單的來說,保守式GC(Conservative GC)指的是"不能識別指針和非指針的GC"。

總結

本文簡單的介紹了JVM中幾個基本的垃圾回收算法,主要是標記-清除算法,標記-複製算法和標記-整理算法。每個算法都有各自的優缺點。一般而言新生代採用標記-清除算法和標記-複製算法居多,老年代會採用標記-整理算法。

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