1. 程式人生 > >深入理解JVM之垃圾收集演算法

深入理解JVM之垃圾收集演算法

我們這裡將介紹4中垃圾收集演算法

  1. 標記-清除演算法
  2. 複製演算法
  3. 標記整理演算法
  4. 分代收集演算法

標記-清除演算法

標記-清除演算法是最基礎的演算法,顧名思義,這種演算法分為兩個過程,標記和清除兩個階段。這裡物件是如何進行標記或者判斷物件是否需要清除的呢,就是我們之前文章中提到的引用計數法和可達性分析演算法,當然,比較主流的是可達性分析演算法。在完成對物件的標記之後,就是清除工作了。之所以說這種演算法是最基礎的垃圾收集演算法,主要有兩點:

  • 效率問題:標記和清除兩個過程的效率都不高。
  • 空間問題:標記清除之後會導致產生許多不連續的記憶體碎片,空間碎片過多,可能導致以後在程式執行過程中可能需要分配比較大的物件是無法找到足夠連續的記憶體空間而不得不提前觸發一次垃圾收集動作。

下面是標記-清除演算法的示意圖:

這裡橙色是在使用的物件記憶體,黃色是可回收的物件記憶體,白色是未被佔用的空間。上邊是回收記憶體之前的狀態,下圖是垃圾收集之後的記憶體狀態。

複製演算法

為了解決效率的問題,一種稱為“複製”的收集演算法出現了,它將可用記憶體按容量分配為大小相等的兩部分,每次只使用其中的一塊。當這一塊的記憶體用完了,就將還存活著的物件複製到另外一塊上面,然後在將已經使用過的記憶體空間進一步的清理,這樣使得每一次對整個半區進行記憶體回收,記憶體分配是也就不用考慮記憶體空間碎片的問題,只要移動堆頂指標,按順序分配記憶體即可,實現簡單,執行高效。只是這種演算法的代價是將整個記憶體壓縮為原來的一半,以空間的代價獲取效率上的提高,這個代價是比較高的。

現在的商用虛擬機器都是採用這種演算法來回收新生代。根據IBM公司的專門研究發現,新生代中的物件98%的是“朝生夕死”,所以根部不需要按照1:1的比率劃分記憶體空間,而是將記憶體空間分為比較大的Eden空間和兩塊比較小的Servivor空間,每次使用Eden和其中的一塊Servivor空間。當回收時,將Eden和Servivor中的還存活的物件一次向的複製到另一個Servivor記憶體上,最後清除Eden和剛才使用過的Servivor區域的空間;HotSpot虛擬機器預設的Eden和Servivor比率是8:1。當Survivor空間不夠用時,需要其它的記憶體(老年代)進行分配擔保。

這裡兩個Survivor相當於上面所說的兩個1:1的記憶體,只不過這裡每次記憶體的可利用率在90%。

標記-整理演算法

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

根據老年代的特點,標記整理演算法和標記清除演算法一樣,只不過後續步驟不是直接對可回收物件進行清理,而是讓所存活的物件都向一端移動,然後直接清理掉端邊界以外的記憶體。

將左邊區域需要收集的物件移動到分界線右邊,將右邊不需要回收的物件移動到邊界線左邊,移動完之後,邊界線左邊都是不需要清理的,右邊都是要清理的。

分代收集演算法

當前商業虛擬機器的垃圾回收演算法都才用分代收集演算法,這種演算法並沒有什麼新的思想,只是根據物件存活的週期的不同將記憶體分配為幾塊,一般是把Java堆分為新生代和老年代,這樣就可以根據各個年代的特點選擇合適的垃圾收集演算法,在新生代中,每次垃圾收集時都發現大批物件死去,只有少量存活,那就選用複製演算法,只需要付出少量的存活物件複製的成本就可以完成收集。而老年代因為物件存活率比較高,沒有額外空間進行分配擔保,就必須使用標記-清除或者標記-整理演算法來進行垃圾收集。