Java 虛擬機器(四)垃圾收集演算法

劉望舒發表於2019-02-28

相關文章
Java虛擬機器系列

前言

在本系列上一篇文章中我講到了垃圾標記演算法,垃圾被標記後,GC就會對垃圾進行收集,垃圾收集有很多種演算法,這篇文章就來介紹常用的垃圾收集演算法的思想。

1.標記-清除演算法

標記-清除演算法(Mark-Sweep)是一種常見的基礎垃圾收集演算法,它將垃圾收集分為兩個階段:

  • 標記階段:標記出可以回收的物件。
  • 清除階段:回收被標記的物件所佔用的空間。

標記-清除演算法之所以是基礎的,是因為後面講到的垃圾收集演算法都是在此演算法的基礎上進行改進的。標記-清除演算法的執行的過程如下圖所示。

Java 虛擬機器(四)垃圾收集演算法

標記-清除演算法主要有兩個缺點,一個是標記和清除的效率都不高,另一個從上圖就可以看出來,就是容易產生大量不連續的記憶體碎片,碎片太多可能會導致後續沒有足夠的連續記憶體分配給較大的物件,從而提前觸發新的一次垃圾收集動作。

2.複製演算法

為了解決標記-清除演算法的效率不高的問題,產生了複製演算法。它把記憶體空間劃為兩個相等的區域,每次只使用其中一個區域。垃圾收集時,遍歷當前使用的區域,把存活物件複製到另外一個區域中,最後將當前使用的區域的可回收的物件進行回收。複製演算法的執行過程如下圖所示。

Java 虛擬機器(四)垃圾收集演算法

這種演算法每次都對整個半區進行記憶體回收,不需要考慮記憶體碎片的問題,代價就是使用記憶體為原來的一半。
複製演算法的效率跟存活物件的數目多少有很大的關係,如果存活物件很少,複製演算法的效率就會很高。由於絕大多數物件的生命週期很短,並且這些生命週期很短的物件都存於新生代中,所以複製演算法被廣泛應用於新生代中,關於新生代中複製演算法的應用,會在後面的分代收集演算法中詳細介紹。

3.標記-壓縮演算法

在新生代中可以使用複製演算法,但是在老年代就不能選擇複製演算法了,因為老年代的物件存活率會較高,這樣會有較多的複製操作,導致效率變低。標記-清除演算法可以應用在老年代中,但是它效率不高,在記憶體回收後容易產生大量記憶體碎片。因此就出現了一種標記-壓縮演算法(Mark-Compact)演算法,與標記-清除演算法不同的是,在標記可回收的物件後將所有存活的物件壓縮到記憶體的一端,使他們緊湊的排列在一起,然後對端邊界以外的記憶體進行回收。回收後,已用和未用的記憶體都各自一邊,如下圖所示。

Java 虛擬機器(四)垃圾收集演算法

標記-壓縮演算法解決了標記-清除演算法效率低和容易產生大量記憶體碎片的問題,它被廣泛的應用於老年代中。

4.分代收集演算法

Java堆區的空間劃分

在Java虛擬機器中,各種物件的生命週期會有著較大的差別,大部分物件生命週期很短暫,少部分物件生命週期很長,有的甚至和應用程式以及Java虛擬機器的執行週期一樣長。因此,應該對不同生命週期的物件採取不同的收集策略,根據生命週期長短將它們分別放到不同的區域,並在不同的區域採用不同的收集演算法,這就是分代的概念。
現在主流的Java虛擬機器的垃圾收集器都採用分代收集演算法(Generational Collection)。Java堆區基於分代的概念,分為新生代(Young Generation)和老年代(Tenured Generation),其中新生代再細分為Eden空間、From Survivor空間和To Survivor空間。因為Eden空間大多物件生命週期很短,所以新生代的空間劃分並不是均分的,HotSpot虛擬機器預設Eden空間和兩個Survivor空間的所佔的比例為8:1。

分代收集

根據Java堆區的空間劃分,垃圾收集的型別分為兩種,它們分別是:

  • Minor Collection:新生代垃圾收集。
  • Full Collection:對新生代、老年代和永久代(JDK8 取消永久代,Full Collection掃描不到替代永久代的元空間)進行收集,又可以稱作Majjor Collection。它的收集頻率較低,耗時較長。

當執行一次Minor Collection時,Eden空間的存活物件會被複制到To Survivor空間,並且之前經過一次Minor Collection並在From Survivor空間存活的仍年輕的物件也會複製到To Survivor空間。
有兩種情況Eden空間和From Survivor空間存活的物件不會複製到To Survivor空間,而是晉升到老年代。一種是存活的物件的分代年齡超過-XX:MaxTenuringThreshold(用於控制物件經歷多少次Minor GC才晉升到老年代)所指定的閾值。另一種是To Survivor空間容量達到閾值。
當所有存活的物件被複制到To Survivor空間,或者晉升到老年代,也就意味著Eden空間和From Survivor空間剩下的都是可回收物件,如下圖所示。

Java 虛擬機器(四)垃圾收集演算法

這時GC執行Minor Collection,Eden空間和From Survivor空間都會被清空,而存活的物件都存放在To Survivor空間。
接下來將From Survivor空間和To Survivor空間互換位置,也就是此前的From Survivor空間成為了現在的To Survivor空間,每次Survivor空間互換都要保證To Survivor空間是空的,這就是複製演算法在新生代中的應用。在老年代則採用了標記-壓縮演算法。
在HotSpot中,基於分代的概念,GC使用的回收演算法針對新生代和老年代的特點,採用不同的垃圾收集演算法。

參考資料
《深入理解 Java 虛擬機器:JVM 高階特性與最佳實踐》第二版
《Java虛擬機器精講》
《HotSpot實戰》


歡迎關注我的微信公眾號,第一時間獲得部落格更新提醒,以及更多成體系的Android相關原創技術乾貨。
掃一掃下方二維碼或者長按識別二維碼,即可關注。

Java 虛擬機器(四)垃圾收集演算法

相關文章