在說垃圾回收算法之前,先談談JVM怎樣確定哪些對象是“垃圾”。
引用計數器算法是給每個對象設置一個計數器,當有地方引用這個對象的時候,計數器+1,當引用失效的時候,計數器-1,當計數器為0的時候,JVM就認為對象不再被使用,是“垃圾”了。
引用計數器實現簡單,效率高;但是不能解決循環引用問問題(A對象引用B對象,B對象又引用A對象,但是A,B對象已不被任何其他對象引用),同時每次計數器的增加和減少都帶來了很多額外的開銷,所以在JDK1.1之後,這個算法已經不再使用了。
根搜索方法是通過一些“GC Roots”對象作為起點,從這些節點開始往下搜索,搜索通過的路徑成為引用鏈(Reference Chain),當一個對象沒有被GC Roots的引用鏈連接的時候,說明這個對象是不可用的。
GC Roots對象包括:
a) 虛擬機棧(棧幀中的本地變量表)中的引用的對象。
b) 方法區域中的類靜態屬性引用的對象。
c) 方法區域中常量引用的對象。
d) 本地方法棧中JNI(Native方法)的引用的對象。
了解了JVM是怎麼確定對象是“垃圾”之後,進入正題,讓我們來看看垃圾回收的算法。
標記—清除算法包括兩個階段:“標記”和“清除”。在標記階段,確定所有要回收的對象,並做標記。清除階段緊隨標記階段,將標記階段確定不可用的對象清除。
標記—清除算法是基礎的收集算法,標記和清除階段的效率不高,而且清除後回產生大量的不連續空間,這樣當程序需要分配大內存對象時,可能無法找到足夠的連續空間。
垃圾回收前:
垃圾回收後:
綠色:存活對象 紅色:可回收對象 白色:未使用空間
復制算法是把內存分成大小相等的兩塊,每次使用其中一塊,當垃圾回收的時候,把存活的對象復制到另一塊上,然後把這塊內存整個清理掉。
復制算法實現簡單,運行效率高,但是由於每次只能使用其中的一半,造成內存的利用率不高。現在的JVM用復制方法收集新生代,由於新生代中大部分對象(98%)都是朝生夕死的,所以兩塊內存的比例不是1:1(大概是8:1)。
垃圾回收前:
垃圾回收後:
綠色:存活對象 紅色:可回收對象 白色:未使用空間
標記—整理算法和標記—清除算法一樣,但是標記—整理算法不是把存活對象復制到另一塊內存,而是把存活對象往內存的一端移動,然後直接回收邊界以外的內存。
標記—整理算法提高了內存的利用率,並且它適合在收集對象存活時間較長的老年代。
垃圾回收前:
垃圾回收後:
綠色:存活對象 紅色:可回收對象 白色:未使用空間
分代收集是根據對象的存活時間把內存分為新生代和老年代,根據個代對象的存活特點,每個代采用不同的垃圾回收算法。新生代采用標記—復制算法,老年代采用標記—整理算法。
垃圾算法的實現涉及大量的程序細節,而且不同的虛擬機平台實現的方法也各不相同。上面介紹的只不過是基本思想。