1. 程式人生 > >JVM(五):GC垃圾收集器分類

JVM(五):GC垃圾收集器分類

1、Serial收集器新生代

單執行緒,在進行垃圾收集時必須暫停其他所有的工作執行緒(“Stop the World“)。虛擬機器執行在Client模式下的預設新生代收集器。簡單而高效(與其他收集器的單執行緒比),對於限定單個CPU的環境來說,Serial收集器由於沒有執行緒互動的開銷,專心做垃圾收集自然可以獲得最高的單執行緒效率。

2、ParNew收集器新生代

ParNew收集器其實是前面Serial的多執行緒版本,除使用多條執行緒進行GC外,包括Serial可用的所有控制引數、收集演算法、STW、物件分配規則、回收策略等都與Serial完全一樣(也是VM啟用CMS收集器-XX: +UseConcMarkSweepGC的預設新生代收集器)。

由於存線上程切換的開銷,ParNew在單CPU的環境中比不上Serial,且在通過超執行緒技術實現的兩個CPU的環境中也不能100%保證能超越Serial。 但隨著可用的CPU數量的增加,收集效率肯定也會大大增加(ParNew收集執行緒數與CPU的數量相同,因此在CPU數量過大的環境中,可用-XX:ParallelGCThreads引數控制GC執行緒數)。

3、Parallel Scavenge收集器(“吞吐量優先收集器)(新生代

        使用複製演算法,並行多執行緒,這些特點與ParNew一樣,它的獨特之處是它的關注點與其他收集器不同,CMS等收集器的關注點是儘可能縮短垃圾收集時使用者執行緒的停頓時間,而Parallel Scavenge收集器的目的則是達到一個可控制的吞吐量

Throughput),即CPU用於執行使用者程式碼的時間與CPU總消耗時間的比值,吞吐量=執行使用者程式碼時間 /執行使用者程式碼時間+垃圾收集時間),虛擬機器總共運行了100分鐘,其中垃圾收集花掉1分鐘,吞吐量就是99%。

       停頓時間越短對於需要與使用者互動的程式來說越好,良好的響應速度能提升使用者的體驗;

       高吞吐量可以最高效率地利用CPU時間,儘快地完成程式的運算任務,主要適合在後臺運算而不太需要太多互動的任務。

引數設定:

-XXMaxGCPauseMillis 控制最大垃圾收集停頓時間。(大於0的毫秒數)停頓時間縮短是以犧牲吞吐量和新生代空間換取的。(新生代調的小,吞吐量跟著小,垃圾收集時間就短,停頓就小)。

-XXGCTimeRatio 直接設定吞吐量大小,0<x<100 的整數,允許的最大GC時間=1/1+x)。

-XX+UseAdaptiveSizePolicy  一個開關引數,開啟GC自適應調節策略(GC Ergonomics),將記憶體管理的調優任務(新生代大小-Xmn、Eden與Survivor區的比例-XX:SurvivorRatio、晉升老年代物件年齡-XX: PretenureSizeThreshold 、等細節引數)交給虛擬機器完成。這是Parallel Scavenge收集器與ParNew收集器的一個重要區別,另一個是吞吐量。

4、Serial Old收集器老年代

Serial Old是Serial收集器的老年代版本,同樣是單執行緒收集器,使用標記-整理演算法

Serial Old應用場景如下:

  • JDK 1。5之前與Parallel Scavenge收集器搭配使用;
  • 作為CMS收集器的後備預案,在併發收集發生Concurrent Mode Failure時啟用(見下:CMS收集器)。

5、Parallel Old收集器老年代

它是Parallel Scavenge收集器的老年代版本,多執行緒,使用“標記-整理“演算法。在注重吞吐量及CPU資源敏感的場合,都可以優先考慮Parallel Scavenge+Parallel Old收集器。工作過程如下:

6、CMS收集器老年代

它是一種以獲取最短回收停頓時間為目標的收集器。優點併發收集,低停頓。基於“標記-清除“演算法。目前很大一部分Java應用都集中在網際網路站或B/S系統的服務端上,這類應用尤其重視服務的響應速度,希望系統停頓時間最短,以給使用者帶來較好的體驗,CMS收集器就非常符合這類應用的需求。運作過程較複雜,分為4個步驟:

  • 初始標記(CMS initial mark):需要“Stop The World“,但初始標記僅標記一下GC Roots能直接關聯到的物件,速度很快。
  • 併發標記(CMS concurrent mark):進行GC Roots Tracing 過程
  • 重新標記(CMS remark):需要“Stop The World“,修正併發標記期間,因使用者程式繼續執行而導致標記產生變動的那一部分物件的標記記錄。停頓時間:初始標記<重新標記<<併發標記
  • 併發清除(CMS concurrent sweep):時間較長。

由於CMS收集器將整個GC過程進行了更細粒度的劃分,因此可以實現併發收集、低停頓的優勢,但它也並非十分完美,其存在缺點及解決策略如下:

        1、CMS預設啟動的回收執行緒數=(CPU數目+3)/4

當CPU數>4時,GC執行緒最多佔用不超過25%的CPU資源,但是當CPU數<=4時,GC執行緒可能就會過多的佔用使用者CPU資源,從而導致應用程式變慢,總吞吐量降低。

        2、無法處理浮動垃圾可能出現Promotion Failure、Concurrent Mode Failure而導致另一次Full GC的產生。浮動垃圾是指在CMS併發清理階段使用者執行緒執行而產生的新垃圾。 由於在GC階段使用者執行緒還需執行,因此還需要預留足夠的記憶體空間給使用者執行緒使用,導致CMS不能像其他收集器那樣等到老年代幾乎填滿了再進行收集。因此CMS提供了-XX:CMSInitiatingOccupancyFraction引數來設定GC的觸發百分比,當老年代的使用空間超過該比例後CMS就會被觸發(JDK 1.6之後預設92%)。 但當CMS執行期間預留的記憶體無法滿足程式需要,就會出現上述Promotion Failure等失敗,這時VM將啟動後備預案: 臨時啟用Serial Old收集器來重新執行Full GC(CMS通常配合大記憶體使用,一旦大記憶體轉入序列的Serial GC,那停頓的時間就是大家都不願看到的了)。

      3、最後,由於CMS採用”標記-清除”演算法實現,可能會產生大量記憶體碎片。 記憶體碎片過多可能會導致無法分配大物件而提前觸發Full GC。 因此CMS提供了-XX:+UseCMSCompactAtFullCollection開關引數,用於在Full GC後再執行一個碎片整理過程。 但記憶體整理是無法併發的,記憶體碎片問題雖然沒有了,但停頓時間也因此變長了,因此CMS還提供了另外一個引數-XX:CMSFullGCsBeforeCompaction用於設定在執行N次不進行記憶體整理的Full GC後,跟著來一次帶整理的(預設為0: 每次進入Full GC時都進行碎片整理)。

7、G1收集器Garbage First

G1(Garbage-First)是一款面向服務端應用的收集器,主要目標用於配備多顆CPU的伺服器治理大記憶體。G1垃圾收集器被認為是GMS的長期可替代方案,JDK1.7之後的預設垃圾收集器。

- -XX:+UseG1GC 啟用G1收集器。

與其他基於分代的收集器不同,G1將整個Java堆劃分為多個大小相等的獨立區域(Region),雖然還保留有新生代和老年代的概念,但新生代和老年代不再是物理隔離的了,它們都是一部分Region(不需要連續)的集合。

每塊區域既有可能屬於O區、也有可能是Y區,因此不需要一次就對整個老年代/新生代回收。 而是當執行緒併發尋找可回收的物件時有些區塊包含可回收的物件要比其他區塊多很多 雖然在清理這些區塊時G1仍然需要暫停應用執行緒但可以用相對較少的時間優先回收垃圾較多的Region(這也是G1命名的來源)。 這種方式保證了G1可以在有限的時間內獲取儘可能高的收集效率。

新生代收集

G1的新生代收集跟ParNew類似: 存活的物件被轉移到一個/多個Survivor Regions。 如果存活時間達到閥值,這部分物件就會被提升到老年代。

G1的新生代收集特點如下:

  • 一整塊堆記憶體被分為多個Regions。
  • 存活物件被拷貝到新的Survivor區或老年代。
  • 年輕代記憶體由一組不連續的heap區組成,這種方法使得可以動態調整各代區域尺寸。
  • Young GCs會有STW事件,進行時所有應用程式執行緒都會被暫停。
  • 多執行緒併發GC。

老年代收集

G1老年代GC會執行以下階段:

注: 以下有些階段也是年輕代垃圾收集的一部分。

index Phase Description
(1) 初始標記 (Initial Mark: Stop the WorldEvent) 在G1中,該操作附著一次年輕代GC,以標記Survivor中有可能引用到老年代物件的Regions。
(2) 掃描根區域 (Root Region Scanning: 與應用程式併發執行) 掃描Survivor中能夠引用到老年代的references。 但必須在Minor GC觸發前執行完。
(3) 併發標記 (Concurrent Marking : 與應用程式併發執行) 在整個堆中查詢存活物件,但該階段可能會被Minor GC中斷。
(4) 重新標記 (Remark : Stop the WorldEvent) 完成堆記憶體中存活物件的標記。 使用snapshot-at-the-beginningSATB起始快照)演算法,比CMS所用演算法要快得多(空Region直接被移除並回收,並計算所有區域的活躍度)。
(5) 清理 (Cleanup : Stop the World Event and Concurrent) 見下 5-1、5-2、5-3
  5-1 (Stop the world 在含有存活物件和完全空閒的區域上進行統計
  5-2 (Stop the world 擦除Remembered Sets。
  5-3 (Concurrent) 重置空regions並將他們返還給空閒列表(free list)
(*) Copying/Cleanup (Stop the WorldEvent) 選擇”活躍度”最低的區域(這些區域可以最快的完成回收)。 拷貝/轉移存活的物件到新的尚未使用的regions。 該階段會被記錄在gc-log內(只發生年輕代[GC pause (young)],與老年代一起執行則被記錄為[GC Pause (mixed)]

 

詳細步驟可參考 Oracle官方文件-The G1 Garbage Collector Step by Step

  • G1老年代GC特點如下:
    • 併發標記階段(index 3)
      1. 在與應用程式併發執行的過程中會計算活躍度資訊。
      2. 這些活躍度資訊標識出哪些regions最適合在STW期間回收(which regions will be best to reclaim during an evacuation pause)。
      3. 不像CMS有清理階段。
    • 重新標記階段(index 4)
      1. 使用Snapshot-at-the-Beginning(SATB)演算法比CMS快得多。
      2. 空region直接被回收。
    • 拷貝/清理階段(Copying/Cleanup Phase)
      • 年輕代與老年代同時回收。
      • 老年代記憶體回收會基於他的活躍度資訊。

 

補充: 關於Remembered Set

G1收集器中,Region之間的物件引用以及其他收集器中的新生代和老年代之間的物件引用都是使用Remembered Set來避免掃描全堆。 G1中每個Region都有一個與之對應的Remembered Set,VM發現程式對Reference型別資料進行寫操作時,會產生一個Write Barrier暫時中斷寫操作,檢查Reference引用的物件是否處於不同的Region中(在分代例子中就是檢查是否老年代中的物件引用了新生代的物件),如果是,便通過CardTable把相關引用資訊記錄到被引用物件所屬的Region的Remembered Set中。 當記憶體回收時,在GC根節點的列舉範圍加入Remembered Set即可保證不對全域性堆掃描也不會有遺漏。