由于垃圾收集算法的實作涉及大量的程式細節,而且每個平台的虛拟機操作記憶體的方法又各不相同,是以部落格中不過多的讨論算法的實作,隻是介紹幾種算法的思想以及發展。
相關閱讀:
1、深入了解java虛拟機之java記憶體區域
2、深入了解java虛拟機之對象真的死了嗎
标記清除算法分為“标記”和“清除”兩個階段,首先先标記出那些對象需要被回收,在标記完成後會對這些被标記了的對象進行回收;如下圖:

這種算法的優點在于不需要對對象進行移動操作,僅對不存活的對象進行操作,是以在對象存活率較高的情況下效率非常高,但是從上圖模拟的結果來看對象被回收後,可用的記憶體并不是連續的,而是斷斷續續,造成大量的記憶體碎片。 存儲對象時要求記憶體空間時連續的,是以虛拟機在給新的記憶體較大的對象配置設定空間時,有可能找不到足夠大的連續的空閑的空間來存放,進而引發一次垃圾回收動作,實際上裡面是有大量的空閑空間的,隻是不連續而已。
複制算法是将記憶體分為兩塊大小一樣的區域,每次是使用其中的一塊。當這塊記憶體塊用完了,就将這塊記憶體中還存活的對象複制到另一塊記憶體中,然後清空這塊記憶體。這種算法在對象存活率較低的場景下效率很高,比如說新生代,隻對整塊記憶體區域的一半進行垃圾回收,在垃圾回收的過程也不會出現記憶體碎片的情況,不需要移動對象,隻需要移動指針即可,實作簡單,是以運作效率很高。運作效率是在建立在浪費空間的基礎上的,這是典型的已空間換時間的方法,因為每次隻能是使用北村的一半。算法示意圖如下:
現在商用的jvm中都采用了這種算法來回收新生代,因為新生代的對象基本上都是朝生夕死的,存活下來的對象約占10%左右,是以需要複制的對象比較少,采用這種算法效率比較高。hotspot版本的虛拟機将堆(heap)記憶體分為了新生代和老年代,其中新生代又分為記憶體較大的Eden區和兩個較小的survivor區。當進行記憶體回收時,将eden區和survivor區的還存活的對象一次性地複制到另一個survivor空間上,最後将eden區和剛才使用過的survivor空間清理掉。hotspot虛拟機預設eden和survivor空間的大小比例為8:1,也就是每次新生代中可用記憶體空間為整個新生代空間的90%(80%+10%),隻會浪費掉10%的空間。當然,98%的對象可回收隻是一般場景下的資料,我們沒有辦法保證每次回收都隻有不多于10%的對象存活,當survivor空間不夠用時,需要依賴于其他記憶體(這裡指的是老年代)進行配置設定的擔保。
複制算法在對象存活率較高的情況下就要進行較多的對象複制操作,效率将會變低。更關鍵的是,如果你不需要浪費50%的空間,就需要有額外的空間進行配置設定擔保,用以應對被使用的記憶體中所有對象都100%存活的極端情況,是以在老年代一般不能直接選用這種辦法。
根據老年代的特點,有人提出了标記-整理的算法,标記過程仍然與标記-清楚算法一樣,但後續步驟不是直接将可回收對象清理掉,而是讓所有存活的對象都向一端移動,然後直接清理掉端邊界以外的記憶體,算法示意圖如下:
分代收集算法将heap區域劃分為新生代和老年代,新生代的空間比老年代的空間要小。新生代又分為了Eden和兩個survivor空間,它們的比例為8:1:1。對象被建立時,記憶體的配置設定是在新生代的Eden區發生的,大對象直接在老年代配置設定記憶體,IBM的研究表明,Eden區98%的對象都是很快消亡的。
為了提高gc效率,分代收集算法中新生代和老年代的gc是分開的,新生代發生的gc動作叫做minor gc 或 young gc,老年代發生的叫做major gc 或 full gc。
minor gc 的觸發條件:當建立新對象時Eden區剩餘空間小于對象的記憶體大小時發生minor gc;
major gc 觸發條件:
1、顯式調用System.gc()方法;
2、老年代空間不足;
3、方法區空間不足;
4、從新生代進入老年代的空間大于老年代空閑空間;
Eden區對象的特點是生命周期短,存活率低,是以Eden區使用了複制算法來回收對象,上面也提到複制算法的特點是在存活率較低的情況下效率會高很多,因為需要複制的對象少。與一般的複制算法不同的是,一般的複制算法每次隻能使用一半的空間,另一半則浪費掉了,Eden區的回收算法也叫做"停止-複制"算法,當Eden區空間已滿時,觸發Minor GC,清理掉無用的對象,然後将存活的對象複制到survivor1區(此時survivor0有存活對象,survivor1為空的),清理完成後survivor0為空白空間,survivor1有存活對象,然後将survivor0和survivor1空間的角色對象,下次觸發Minor gc時重複上述過程。如果survivor1區剩餘空間小于複制對象所需空間時,将對象配置設定到老年代中。每發生一次Minor gc時,存活下來的對象的年齡則會加1,達到一定的年齡後(預設為15)該對象就會進入到老年代中。
老年代的對象基本是經過多次Minor gc後存活下來的,是以他們都是比較穩定的,存活率高,如果還是用複制算法顯然是行不通的。是以老年代使用“标記-整理”算法來回收對象的,進而提高老年代回收效率。
總的來說,分代收集算法并不是一種具體的算法,而是根據每個年齡代的特點,多種算法結合使用來提高垃圾回收效率。
參考資料:《深入了解Java虛拟機》
原文 Java垃圾收集算法