1. 程式人生 > >jvm 三種垃圾回收演算法:標記-清除、複製演算法、標記-整理

jvm 三種垃圾回收演算法:標記-清除、複製演算法、標記-整理

標記-清除:先標記出GC Roots能關聯到的物件,然後清除這些被標記的物件,剩下的就是存活的物件了。

缺點:

1、清除需要被清理的物件後剩下的記憶體都是破碎的,如果要建立大物件,可能會因為找不到足夠的記憶體而再次觸發垃圾收集。

2、標記和清除的效率相對於其他演算法來說都不高,標記的原理就是從GC Roots往下遍歷,能被遍歷到的物件就是存活物件,剩下不能被遍歷到的物件就是需要被標記清除的。而清除時,是根據標記,然後一個一個清除。。這個效率就比較低了。

複製演算法:大致原理,把記憶體分為兩塊,分別命名為A、B,A用於建立物件,B則是空閒記憶體(暫時不存放任何物件),當要進行垃圾回收時,把A中能被GC roots關聯到的物件全部複製到B中,然後把A中的所有記憶體空閒都清理掉。接下來就把B記憶體當作A,A當作B,當再次要進行垃圾回收時,重複以上步驟。

複製演算法適用於年輕代。

優點:

(1)記憶體總有一部分是完整的一塊,不會出現破碎記憶體的情況。

(2)一般情況下,存活物件只佔所有物件中的很少一部分,百分之九十以上的物件都是要被清理掉的,所以複製存活物件到另一塊記憶體中所操作的物件數量就比較少,這樣的話,自然而然效率就高了。像標記-清除演算法,是先要標記出所有不能存活的物件才行,意味著要對大多數物件進行操作。

缺點:

1、缺點其實也很明顯,複製演算法就是使用一部分記憶體作為備用,把這塊記憶體用於儲存存活下來的物件,每次垃圾回收都如此,那就意味著作為備用的那一塊記憶體是被浪費了的。所以,複製演算法的缺點就是會浪費一部分記憶體。

2、不僅需要一部分記憶體作為備用,還需要其他記憶體作為擔保。當存活的物件佔用記憶體超出備用記憶體的儲存空間怎麼辦?那麼就需要使用擔保記憶體了,把一部分存活率高的物件放到老年代中,以此使得備用記憶體的空間足夠存放剩下的物件。

 

 

標記-整理演算法:標記出所有能存活的物件,然後這些存活的物件向記憶體的某一端移動。。把這一端記憶體從頭到尾佔滿,那整塊記憶體除了這一端之外的空間就都是垃圾物件了。

適用於老年代。

舉個例子:陣列Object[] array=new Object[10];被當做記憶體

該陣列所有元素都有物件,其中,0、2、3、5、7、9都是能夠存活的物件,其他的是垃圾物件。

在標記-整理演算法中,移動存活物件從後往前移,用存活的物件填補垃圾物件的空缺,像上面那樣,9移動到1,7移動到4,這樣的話存活下來的物件在0到5之間了,在這個範圍以外的物件都是垃圾物件,直接清理。當然,移動演算法不是這麼簡單的,應該是找大小合適的存活物件去填補空缺,使得記憶體的一端佔用空閒儘量的滿。

優點:

1、不會像複製演算法一樣一定要浪費一部分記憶體用於儲存存活的物件,不需要像複製演算法一樣需要擔保記憶體。

2、被清理的出來的記憶體是一整塊完整的記憶體空間,而不是破碎的。標記清除演算法清理出來的記憶體是破碎的,容易再觸發GC。

3、在老年代這種物件存活率高的情況下,“整理”這個步驟所花費的消耗也是不高的。

4、但標記整理的記憶體利用率高。比複製演算法高,複製演算法有一部分必需浪費。

缺點:當物件存活率低的時候使用複製演算法的效率明顯更高,因為“標記”這個步驟是需要從GC Roots往關聯物件遍歷的。所以“標記”的效率也是低的。。。

 

 

總結:

複製演算法:實現簡單,執行效率高,但每次只能使用一半記憶體,因此記憶體的利用率不高;

標記整理演算法,涉及兩個過程,執行效率慢,且整理之後會產生不連續的記憶體空間