HotSpot虛擬機的垃圾收集器

垃圾收集器就是使用一種或者多種垃圾回收算法,在不一樣的內存分配策略下,進行垃圾回收的程序。因爲內存分配策略不一樣的緣由,因此垃圾回收算法以及垃圾收集器的種類也不盡相同,下圖是JDK1.7 Update 14以後的HotSpot虛擬機所包含的全部的垃圾收集器。java

                    

HotSpot中的收集器分類算法

 

圖上整體分爲兩大類,年輕代(Young generation)和老年代(Tenured generation)。上圖中的連線表示在年輕代和老年代之間,哪些收集器能夠兩兩配合使用,「八字不合」的收集器是不能在一塊兒的!注意到,G1收集器是當前最前沿的收集器,它不須要和其餘收集器配合,本身就能完成年輕代和老年代的垃圾回收工做。多線程

 

1.Serial收集器併發

Serial收集器應該算是收集器家族中元老級的任務,它是一款歷史比較悠久的收集器,在JDK1.3以前仍是新生代收集器的惟一選擇。Serial的中文意思是「串行」,也就是說他是一個單線程工做的垃圾收集器,其全部的工做都是在單個線程單個CPU的狀況下完成的。除了單線程以外,Serial收集器在進行垃圾回收的過程當中,須要暫停其餘全部的用戶線程,JAVA專家稱這種狀況爲:「Stop The World」。這種好比真的是再形象不過了,由於在那一刻真的好像世界都中止了。有人舉過一個形象的例子,你女友在給你打掃放假的時候,確定不會讓你再處處亂跑,若是她一邊打掃,你一邊亂跑,極可能晚上就要睡沙發了。實際上,「Stop The world」致使的停頓現象,即使是如今的最新的G1垃圾收集器也不可能說徹底避免,而只能不斷地縮短Stop The World的時間。佈局

經過上面的介紹,你們可能以爲Serial收集器多是一個又老又慢過期的收集器,估計已經被淘汰了。然而事實上並不是如此,它到目前爲止依賴是虛擬機運行在client模式下的默認年輕代收集器。咱們先來看一下Serial收集器的工做的過程,以後能夠和後面的幾個收集器在進行對比,就能發現Serial收集器在某些特殊的環境下,仍是有優勢的:性能

        

Serial/Serial Old收集器回收過程優化

 

正式由於Serial的單線程,因此它相比於其餘的收集器來講,優勢就是簡單而高效,對於單個CPU的環境的來講,Serial收集器沒有線程交互,因此回收的效率相對來講就比較高。因此Serial收集器更適合運行在client模式下的虛擬機,如桌面應用等。ui

 

關於JVM Client模式和Server模式:Client模式採用的是輕量級的虛擬機,全部啓動的比較快;Server模式採用的是重量級的虛擬機,因此啓動比較慢。重量級的虛擬機在運行期間作了不少的優化,因此啓動以後程序運行比較快。Client模式通常內存佔用的狀況比較小,VM在client模式默認-Xms是1M,-Xmx是64M;JVM在Server模式默認-Xms是128M,-Xmx是1024M;因此Client在進行Stop The World的時間相對來講不會太長,因此使用簡單快速的Serial收集器來進行垃圾回收時再合適不過的了。線程

 

2.ParNew收集器設計

ParNew名字前面的Paral其實是單詞「parallel」的縮寫,意思是並行。因此從名字就能夠知道ParNew收集器應該是多線程,事實上它就是Serial收集器的多線程的版本,因此,它除了是多線程的以外,其餘的全部的東西像控制參數、收集算法、Stop The World、對象分配規則、回收策略等都與Serial收集器徹底同樣。ParalNew收集器回收過程以下圖所示:

            

ParNew / Seria Old收集器回收過程

 

ParNew收集器相對於Serial收集器除了多線程以外,並無其餘的特色。因此若是在單CPU的狀況下,ParalNew收集器並不會比Serial的性能好,甚至由於存在線程的交互的開銷,ParalNew性能反而更差。另外就是,在JDK1.5時期推出了CMS(Concurrent Mark Sweep),這個幾乎被認爲有劃時代意義的垃圾收集真正實現了垃圾回收的併發操做,也就是用戶線程和GC線程是同時工做的。可是你們從開頭的分類圖上能夠看出,在新生代中,能夠和CMS同時工做的只有Serial和ParNew能夠與之搭配工做。因此,在多核的環境下,ParNew收集器是最佳的選擇。

 

3.Parallel Scavenge收集器

Parallel Scavenge收集器是一個新生代收集器,它也是使用了複製算法的收集器(前面介紹的兩種收集器都是複製算法),並且又是並行的多線程收集器,看起來好像和ParNew收集器沒什麼不一樣。哲學上常說,存在便是合理的,一件東西不會無緣無故的出現,Parallel Scavenge也是同樣,那麼它到底有什麼神奇之處呢?

Parallel Scavenge收集器的特色是它的關注點和其餘的收集器不同,CMS收集器的關注點是儘量是縮短垃圾回收時用戶線程的停頓時間,而Parallel收集器的目標則是達到一個可控制的吞吐量。所謂吞吐量就是CPU用於運行用戶代碼的時間與CPU總消耗時間的比值:

 

          

 

停頓時間越短越適合與用戶交互的程序,由於與用戶交互的程序須要有良好的響應速度來提高用戶的體驗。也就是說,在一系列的用戶操做中,用戶並不但願出現長久停頓的狀況。就比如你正在看某種大片,過程當中每隔一段時間就停頓一下,你確定很不爽。假如這個停頓的時間就是垃圾回收致使的,因此你固然但願這個停頓的時間越短越好。Parallel Scavenge就是用來將吞吐量控制在一個能夠接受的範圍內。Parallel Scavenge提供幾個參數來精確控制吞吐量:

 

參數

參數描述

-XX:MaxGCPauseMillis

該參數容許設置一個大於0的毫秒數,收集器將盡量地保證

內存花費的時間不超過該值

-XX:GCTimeRatio

該參數容許設置一個0~100之間的整數,也就是垃圾收集時間佔用比,至關於吞吐量的倒數

-XX:+UseAdaptiveSizePolicy

該參數是一個開關參數,打開以後就不須要制定新生代的大小(-Xmm),Eden與Suirvive的比例(-XX:SurvivorRatio)、晉升老年代對象大小(-XX:PreTenureSizeThreshold)等細節參數了,虛擬機會根據系統運行狀況,動態調整這些參數

 

注意:雖然Paralle Scavenge的參數是能夠精確控制程序運行的吞吐量的,可是並非GC停頓時間控制越小越好。由於GC停頓時間變短,是以犧牲GC吞吐量和新生代空間來換取的。也就是說,太小的GC停頓時間會致使更頻繁地發生GC。因此,Paralle Scavenge參數要設置在一個合理的範圍內,才能讓系統更好的運行。若是對於這方面沒有經驗的人,可使用參數3,讓系統本身決定吞吐量。

 

4.Serial Old收集器

Serial Old收集器相至關於Serial收集器的老年代版本,使用的是標記-整理的算法,一樣他是一個單線程的收集器。如今這個收集器的主要做用就是在Client模式下,做爲虛擬機老年代的回收的垃圾收集器使用。可是若是你要把用在Server模式中,他就具有下面兩種用途:

  1. 在JDk1.5以前包括1.5的版本,與Parallel Scvenge 收集器搭配使用;
  2. 做爲CMS收集器的後備方案,也就是併發收集發生Conrurrent Mode Failure時做爲兜底的方案使用。

 

Serial Old收集器的工做過程以下圖所示:

            

ParNew / Seria Old收集器回收過程

 

5.Parallel Old收集器

Parallel Old是Parallel Scavenge收集器的老年代版本,使用的是多線程和標記-整理。這個算法是在JDK1.6以後才提供的,因此Serial Old才能夠做爲Parallel Scavenge收集器Jdk1.5以前的年輕代收集器配合使用。因此在JDK1.6出現以前,Parallel Scavenge收集器是比較尷尬的,一方面本身在年輕代中使用的是多線程的,而在老年代中,只有單線程的Serial Old能夠選擇。因此在Parallel Old收集器出現以前,Parallel Scavenge收集器的地位是十分尷尬的。因爲在老年代中Serial Old的效率拖後腿,因此Parallel Scavenge收集器一直處於一種不給力的狀態。

直到Parallel Old收集器出現以後,「吞吐量優先」纔有了比較名副其實的應用組合,因此JDk1.6以後,在注重吞吐量以及CPU資源敏感的場景中,均可以優先考慮Parallel Scavenge加Parallel Old的組合。他們組合的垃圾回收過程以下圖所示:

            

Parallel Scavenge / Parallel Old收集器回收過程

 

5.CMS收集器(Concurrent Mark Sweep)

CMS收集器的目標是以得到最短回收停頓時間爲目標的收集器(注意和Parallel Old收集器加以區分,Parallel Old是目標是精確控制程序運行的吞吐量,即停頓時間可控制)。從名稱上咱們能夠看出來CMS使用的標記-清除的算法,它的運行過程相對來講比較複雜,整個過程能夠分爲4個步驟:

  1. 初始標記(CMS initial mark)
  2. 併發標記(CMS concurrent mark)
  3. 從新標記(CMS remark)
  4. 併發清除(CMS concurrent sweep)

 

咱們能夠先來看一下CMS收集器的垃圾回收過程,以下圖所示:

            

CMS垃圾回收過程

 

從圖上能夠看出,在初始標記和從新標記這兩個階段仍然須要"Stop The World"。初始標記只是標記一下GC Roots能直接關聯到的對象,速度很快。併發標記的階段就是進行GC Roots Tracing的過程,而從新標記的過程就是爲了修正併發標記期間由於用戶程序仍然運行而致使變更的那一部分對象的標記記錄。這個過程當中的時間要比初始標記長一些,可是要遠比並發標記的時間要短。因此CMS把標記階段能夠大體理解爲兩個階段,一個階段是能夠併發標記的階段,不會影響程序運行;另外一個階段就是不能夠併發標記的階段,就是標記過程會影響程序運行,因此這個階段要「Stop The World」。可是由於大部分標記工做已經在第一個階段完成,因此第二部分的時間就很是短。相比於以前的收集器不加以區分的方式,這種方式能夠大大減小由於標記而致使「Stop The World」停頓的時間。

從設計來講,CMS使用的也是多線程的方式來進行垃圾回收,因爲是和用戶線程同時進行,且使用的標記-清除算法,因此它就存在如下三個明顯的缺點:

 

  1. CMS默認啓動的回收線程數是(CPU數量+3)/4,也就是說當CPU數量在4個以上的時候,併發回收時垃圾收集線程數量很多於25%,而且隨着CPU數量的增長而降低。可是當CPU數量不足於4個時,CMS對於用戶程序影響就比較大,由於極可能有很多於一半的線程用來垃圾回收,等於性能一會兒降了一半,是沒法忍受的。因此,使用CMS收集器最好是在4以上的CPU數量的環境下才能有很好的體驗。

 

  1. CMS收集器沒法處理浮動垃圾,浮動垃圾聽着好像是很吊的東西,其實也很好理解。就是由於在併發清理的過程是和用戶線程同時併發進行的,因此在清理的過程當中,會產生新的垃圾,這些新的垃圾已經不能當即回收,只能等到下次垃圾回收的時候再清理。等於說,有一段時間這些沒有用內存空間被浪費掉。並且因爲在垃圾清理的階段,用戶程序在運行,因此老年代中必需要預留一段空間給用戶線程使用。這裏要涉及到一個參數配置:-XX:CMSInitiationOccupancyFraction,這個參數表示老年代使用了多少百分比的時候激活CMS收集器。若是這個參數設置的比較高,那麼CMS垃圾回收的頻率就要低,老年代中的浮動垃圾就會滯留的比較大,那麼致使「Concurrent Mode Failure」的概率就比較大,由於老年代剩餘的空間不足於知足用戶線程的運行,因此會觸發一次Full GC(也就是前面提到的,會使用CMS後背方案,也就是用Serial Old垃圾收集器來進行一次老年代的垃圾回收),這種狀況下停頓的時間就會比較長。頻繁的發生這種事情,性能就會下降,因此這個參數設置對於CMS性能相當重要。

 

  1. CMS使用的是標記-清理的算法來進行垃圾回收的,瞭解標記-清理的算法確定知道,這種算法會致使空間碎片。若是空間碎片過多,那麼在進行大對象空間分配的時候極可能不能知足大對象的連續空間的要求,從而致使了一次Full GC。爲了解決這個問題,CMS收集器提供了一個參數:-XX:UseCMSCompactAtFullCollection,這是一個開關,用來控制CMS收集器頂不住要進行FullGC時開啓內存碎片的合併整理過程。這個過程雖然解決了內存碎片的問題,可是因爲內存碎片整理的過程當中是沒法併發,因此「Stop The World」停頓的時間不得不變長。還有一個參數:-XX:CMSFullGCsBeforeCompaction,這個參數表示執行多少次不壓縮(即不整理空間隨便)Full GC後,進行一次帶壓縮的Full GC。

 

5.G1收集器(Garbage First)

G1是一款面向服務端應用的垃圾收集器,HotSpot團隊的目標是,在將來能夠用G1替換掉JDK1.5中發佈的CMS收集器。與其餘的收集器相比,G1收集器具備以下特色:

 

  • 並行與併發:G1能充分利用多CPU、多核環境下的硬件優點,使用多個CPU(多個CPU或者是多個CPU核心)來縮短「Stop The World」的停頓時間,部分其餘收集器本來須要停頓java線程執行的GC動做,G1收集器仍然能夠經過併發的方式讓java程序繼續運行。
  • 分代收集:與其餘收集器同樣,分代概念在G1中仍然得意保留。雖然G1收集器不須要和其餘的收集器配合就能管理整個GC堆,可是它可以採用不一樣的方式去處理新建立的對象和已經存活了一段時間、熬過了屢次GC的舊對象,以期經過這種方式得到更好的垃圾回收的效果。
  • 空間整合:與CMS的「標記-清理」算法不一樣,G1從總體來看是基於「標記-整理」算法實現的收集器,從局部上來看是基於「複製」算法實現的。無論使用這兩種的哪一種,就不會產生內存碎片。
  • 可預測的停頓:這是G1對象CMS的另外一大優點,兩者雖然都關注下降停頓時間,可是G1除了關注下降停頓時間,可是G1處理這點以外,還能夠創建可預測的停頓時間模型,能讓使用者明確指定一個長度在M毫秒的時間片斷內,消耗在垃圾收集上的時間不得超過N毫秒。

 

在G1以前的其餘收集器進行瘦的範圍都是整個新生代和老年代,G1收集器java堆的內存佈局與其餘收集器有很大的差異,他將整個java堆分爲多個大小相等的獨立區域(Region),雖然還保留有新生代和老年代的概念,但新生代和老年代再也不是物理隔離的了,他們都是一部分Region(不須要連續)的集合。

G1的詳解講解會做爲新的一章來分析,以上關於G1的描述摘抄子(深刻了解虛擬機),後面會有單獨的一章來說G1收集器。

相關文章
相關標籤/搜索