1. 程式人生 > >JVM進階之GC(三)垃圾回收演算法

JVM進階之GC(三)垃圾回收演算法

上篇我們討論了怎麼判斷物件是否存活,判了“死刑”的物件就會在垃圾回收中被回收掉。那麼本文將說說JVM是如何回收垃圾的。

垃圾回收演算法

標記-清除演算法

演算法的過程就如同演算法的名字,分為標記清除兩個階段:首先標記出所有要回收的物件,然後對標記的物件統一清除。演算法很簡單,再看下邏輯圖: 
標記清除過程 
記憶體空間經過標記清除演算法回收的過程一目瞭然,從圖中也能看出這種演算法的不足,即標記清除後會產生大量的不連續的記憶體碎片,如果碎片過多會導致大物件無法分配足夠的空間而不得不觸發垃圾回收。 
另外,標記清除演算法的標記和清除階段效率都不高,所以效率也是個問題。

複製演算法

既然標記清除效率達不到預期,那麼新的回收演算法就被創造出來了–複製演算法。演算法將可用記憶體分成兩塊大小一樣的區域,每次只使用其中的一塊區域,當使用的區域空間不足時就得進行垃圾回收了,而存活的物件就直接轉移至另一塊區域,如此往復。看如下複製演算法邏輯圖: 
複製演算法


每次垃圾回收都是在一塊區域進行,即一半的空間進行記憶體回收。好處是記憶體分配後不會產生記憶體碎片問題,當然缺點也顯而易見,記憶體空間利用率只有一半。 
由於每次垃圾回收需要對存活物件再一次遷移,這種演算法適用於存活物件較少的場景。根據大多數經驗表明,新生代的物件98%都是朝生熄滅的,所以複製演算法適應於新生代的垃圾回收。但是在新生代中,一半的空間利用率實在太低,而且絕大部分物件都會被回收的特性,所以將記憶體分為了較大的eden區和兩個較小的survivor區(s0和s1區),每次使用eden區和其中的一個survivor區,另一個survivor區則用於儲存存活的物件。HotSpot虛擬機器預設eden和survivor的比例是8:1,也就是說空間利用率達到了90%(80%+10%)。

標記-整理演算法

顧名思義,看名字大致與標記-清除演算法類似,其演算法的標記過程還真是一樣,但後續不是直接清除垃圾物件,而是讓所有的存活的物件向一個方向移動,然後再清理掉存活物件邊界之外的記憶體空間。來看圖更清晰: 
標記整理演算法 
上圖與標記清除演算法的圖一比較就很容易發現區別了,標記整理演算法重在整理。 
那麼標記整理演算法相對於複製演算法有什麼優勢呢?複製演算法,在回收物件存活率低的情況下比較合適,如果在物件存活率較高時(比如老年代的垃圾回收),就要進行較多的複製操作,效率明顯變低,所以不會採用複製演算法。而在物件存活較高的區域中,如老年代,採用標記整理演算法是比較合適的。

分代收集演算法

在前面記憶體分代的文章中說過,堆中根據物件的存活時間劃分為新生代、老年代和永久代幾個區域,這裡又可以根據各個年代的特點採用最合適的垃圾回收演算法。新生代絕大部分物件朝生熄滅,只有少部分存活,採用複製演算法最合適不過。老年代物件即使進行了垃圾回收,物件的存活率也高,所以採用標記清除或標記整理演算法都是不錯的選擇。