由於垃圾收集算法的實現涉及大量的程序細節,而且每個平台的虛擬機操作內存的方法又各不相同,因此博客中不過多的討論算法的實現,只是介紹幾種算法的思想以及發展。
相關閱讀:
1、標記-清除算法
標記清除算法分為“標記”和“清除”兩個階段,首先先標記出那些對象需要被回收,在標記完成后會對這些被標記了的對象進行回收;如下圖:
這種算法的優點在於不需要對對象進行移動操作,僅對不存活的對象進行操作,所以在對象存活率較高的情況下效率非常高,但是從上圖模擬的結果來看對象被回收后,可用的內存並不是連續的,而是斷斷續續,造成大量的內存碎片。 存儲對象時要求內存空間時連續的,所以虛擬機在給新的內存較大的對象分配空間時,有可能找不到足夠大的連續的空閑的空間來存放,從而引發一次垃圾回收動作,實際上里面是有大量的空閑空間的,只是不連續而已。
2、復制算法
復制算法是將內存分為兩塊大小一樣的區域,每次是使用其中的一塊。當這塊內存塊用完了,就將這塊內存中還存活的對象復制到另一塊內存中,然后清空這塊內存。這種算法在對象存活率較低的場景下效率很高,比如說新生代,只對整塊內存區域的一半進行垃圾回收,在垃圾回收的過程也不會出現內存碎片的情況,不需要移動對象,只需要移動指針即可,實現簡單,所以運行效率很高。運行效率是在建立在浪費空間的基礎上的,這是典型的已空間換時間的方法,因為每次只能是使用北村的一半。算法示意圖如下:
現在商用的jvm中都采用了這種算法來回收新生代,因為新生代的對象基本上都是朝生夕死的,存活下來的對象約占10%左右,所以需要復制的對象比較少,采用這種算法效率比較高。hotspot版本的虛擬機將堆(heap)內存分為了新生代和老年代,其中新生代又分為內存較大的Eden區和兩個較小的survivor區。當進行內存回收時,將eden區和survivor區的還存活的對象一次性地復制到另一個survivor空間上,最后將eden區和剛才使用過的survivor空間清理掉。hotspot虛擬機默認eden和survivor空間的大小比例為8:1,也就是每次新生代中可用內存空間為整個新生代空間的90%(80%+10%),只會浪費掉10%的空間。當然,98%的對象可回收只是一般場景下的數據,我們沒有辦法保證每次回收都只有不多於10%的對象存活,當survivor空間不夠用時,需要依賴於其他內存(這里指的是老年代)進行分配的擔保。
3、標記-整理算法
復制算法在對象存活率較高的情況下就要進行較多的對象復制操作,效率將會變低。更關鍵的是,如果你不需要浪費50%的空間,就需要有額外的空間進行分配擔保,用以應對被使用的內存中所有對象都100%存活的極端情況,所以在老年代一般不能直接選用這種辦法。
根據老年代的特點,有人提出了標記-整理的算法,標記過程仍然與標記-清楚算法一樣,但后續步驟不是直接將可回收對象清理掉,而是讓所有存活的對象都向一端移動,然后直接清理掉端邊界以外的內存,算法示意圖如下:
4、分代收集算法
分代收集算法將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解憂雜貨鋪】,里面不定期發布一些技術干活,也可以免費獲取大量最新最流行的技術教學視頻。