深入理解JVM(五)——垃圾回收器

飄揚的紅領巾發表於2017-08-21

輕鬆學習JVM(五)——垃圾回收器

    上一篇我們介紹了常見的垃圾回收演算法,不同的演算法各有各的優缺點,在JVM中並不是單純的使用某一種演算法進行垃圾回收,而是將不同的垃圾回收演算法包裝在不同的垃圾回收器當中,使用者可以根據自身的需求,使用不同的垃圾回收器,以便讓自己的java程式效能到達最佳。

在介紹垃圾回收器之前,我們先回顧一下java堆的結構。

堆記憶體回顧

clip_image002

java堆記憶體結構包括:新生代和老年代,其中新生代由一個伊甸區和2個倖存區組成,2個倖存區是大小相同,完全對稱的,沒有任何差別。我們把它們稱為S0區和S1區,也可以稱為from區和to區。

JVM的垃圾回收主要是針對以上堆空間的垃圾回收,當然其實也會針對後設資料區(永久區)進行垃圾回收,在此我們主要介紹對堆空間的垃圾回收。

下面我們介紹幾種垃圾回收器:

序列收集器

顧名思義,序列收集器就是使用單執行緒進行垃圾回收。對新生代的回收使用複製演算法,對老年代使用標記壓縮演算法,這也和我們上一篇介紹的演算法優勢是相吻合的。

序列收集器是最古老最穩定的收集器,儘管它是序列回收,回收時間較長,但其穩定性是優於其他回收器的,綜合來說是一個不錯的選擇。要使用序列收集器,可以在啟動配置時加上以下引數:

-XX:+UseSerialGC

序列回收器的執行流程如下所示:

clip_image004

執行垃圾回收時,應用程式執行緒暫停,GC執行緒開始(開始垃圾回收),垃圾回收完成後,應用程式執行緒繼續執行。注意:在GC執行緒執行過程中使用單執行緒進行序列回收。

並行回收器

並行回收器你可能已經猜到就是使用多執行緒並行回收,不過這裡需要注意的是,針對新生代和老年代,是否都使用並行,有不同的回收器選擇:

1、 ParNew回收器

這個回收器只針對新生代進行併發回收,老年代依然使用序列回收。回收演算法依然和序列回收一樣,新生代使用複製演算法,老年代使用標記壓縮演算法。在多核條件下,它的效能顯然優於序列回收器,如果要使用這種回收器,可以在啟動引數中配置:

-XX:+UseParNewGC

如果要進一步指定併發的執行緒數,可以配置一下引數:

-XX:ParallelGCThreads

ParNew回收器的流程如下圖所示:

clip_image006

在進行垃圾回收時應用程式執行緒依然被暫停,GC執行緒並行開始執行垃圾回收,垃圾回收完成後,應用程式執行緒繼續執行。

2、 Parallel回收器

依然是並行回收器,但這種回收器有兩種配置,一種類似於ParNEW:新生代使用並行回收、老年代使用序列回收。它與ParNew的不同在於它在設計目標上更重視吞吐量,可以認為在相同的條件下它比ParNew更優。要使用這種回收器可以在啟動程式中配置:

-XX:+UseParallelGC

Parallel回收器另外一種配置則不同於ParNew,對於新生代和老年代均適應並行回收,要使用這種回收器可以在啟動程式中配置:

XX:+UseParallelOldGC

Parallel回收器的流程和ParNew的流程是一致的:

clip_image008

在進行回收時,應用程式暫停,GC使用多執行緒併發回收,回收完成後應用程式執行緒繼續執行。

CMS回收器

CMS回收器: Concurrent Mark Sweep,併發標記清除。注意這裡注意兩個詞:併發、標記清除。

並發表示它可以與應用程式併發執行、交替執行;標記清除表示這種回收器不是使用的是標記壓縮演算法,這和前面介紹的序列回收器和併發回收器有所不同。需要注意的是CMS回收器是一種針對老年代的回收器,不對新生代產生作用。這種回收器優點在於減少了應用程式停頓的時間,因為它不需要應用程式完成暫定等待垃圾回收,而是與垃圾回收併發執行。要執行這種垃圾回收器可以在啟動引數中配置:

-XX:+UseConcMarkSweepGC

CMS回收機執行機制非常複雜,我們簡單的將他的執行流程分為以下幾步:

初始標記

標記從GC Root可以直接可達的物件;

併發標記(和應用程式執行緒一起)

主要標記過程,標記全部物件;

重新標記

由於併發標記時,使用者執行緒依然執行,因此在正式清理前,再做依次重新標記,進行修正。

併發清除(和使用者執行緒一起)

基於標記結果,直接清理物件。

流程如下圖所示:

clip_image010

從上圖可以看到標記過程分三步:初始標記、併發標記、重新標記,併發標記是最主要的標記過程,而這個過程是併發執行的,可以與應用程式執行緒同時進行,初始標記和重新標記雖然不能和應用程式併發執行,但這兩個過程標記速度快,時間短,所以對應用程式不會產生太大的影響。最後併發清除的過程,也是和應用程式同時進行的,避免了應用程式的停頓。

CMS的優點顯而易見,就是減少了應用程式的停頓時間,讓回收執行緒和應用程式執行緒可以併發執行。但它也不是完美的,從他的執行機制可以看出,因為它不像其他回收器一樣集中一段時間對垃圾進行回收,並且在回收時應用程式還是執行,因此它的回收並不徹底。這也導致了CMS回收的頻率相較其他回收器要高,頻繁的回收將影響應用程式的吞吐量。

G1回收器

G1回收器是jdk1.7以後推出的回收器,試圖取代CMS回收器。

不同於其他的回收器、G1將堆空間劃分成了互相獨立的區塊。每塊區域既有可能屬於老年代、也有可能是新生代,並且每類區域空間可以是不連續的(對比CMS的老年代和新生代都必須是連續的)。這種將老年代區劃分成多塊的理念源於:當併發後臺執行緒尋找可回收的物件時、有些區塊包含可回收的物件要比其他區塊多很多。雖然在清理這些區塊時G1仍然需要暫停應用執行緒、但可以用相對較少的時間優先回收包含垃圾最多區塊。這也是為什麼G1命名為Garbage First的原因:第一時間處理垃圾最多的區塊。要使用G1回收器需要在啟動是配置以下引數:

-XX:+UseG1GC

G1相對CMS回收器來說優點在於:

1、因為劃分了很多區塊,回收時減小了記憶體碎片的產生;

2、G1適用於新生代和老年代,而CMS只適用於老年代。

小結

    本文簡要介紹了JVM中的垃圾回收器,主要包括序列回收器、並行回收器以及CMS回收器、G1回收器。他們各自都有優缺點,通常來說你需要根據你的業務,進行基於垃圾回收器的效能測試,然後再做選擇。下面給出配置回收器時,經常使用的引數:

-XX:+UseSerialGC:在新生代和老年代使用序列收集器

-XX:+UseParNewGC:在新生代使用並行收集器

-XX:+UseParallelGC :新生代使用並行回收收集器,更加關注吞吐量

-XX:+UseParallelOldGC:老年代使用並行回收收集器

-XX:ParallelGCThreads:設定用於垃圾回收的執行緒數

-XX:+UseConcMarkSweepGC:新生代使用並行收集器,老年代使用CMS+序列收集器

-XX:ParallelCMSThreads:設定CMS的執行緒數量

-XX:+UseG1GC:啟用G1垃圾回收器

 

相關文章