CMS收集器 VS G1

CMS收集器 VS G1

CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間爲目標的收集器。這是因爲CMS收集器工作時,GC工作線程與用戶線程可以併發(Concurrent)執行,以此來達到降低收集停頓時間的目的

CMS GC步驟

  1. 初始標記(STW initial mark)
  2. 併發標記(Concurrent marking)
  3. 併發預清理(Concurrent precleaning)
  4. 重新標記(STW remark)
  5. 併發清理(Concurrent sweeping)
  6. 併發重置(Concurrent reset)


[GC (CMS Final Remark) [YG occupancy: 1120265 K (2146944 K)]2019-08-30T17:59:30.896+0800: 9938.409:
 [Rescan (parallel) , 0.0345201 secs]2019-08-30T17:59:30.930+0800: 9938.444:
 [weak refs processing, 1.3559616 secs]2019-08-30T17:59:32.286+0800: 9939.800:
 [class unloading, 0.0689365 secs]2019-08-30T17:59:32.355+0800: 9939.869: [scrub symbol table, 0.0166362 secs]2019-08-30T17:59:32.372+0800: 9939.885:
 [scrub string table, 0.0013061 secs][1 CMS-remark: 21874968K(29071808K)] 22995234K(31218752K), 1.9651057 secs] [Times: user=2.80 sys=0.07, real=1.96 secs]

CMS缺點

  • CMS回收器採用的基礎算法是Mark-Sweep。所有CMS不會整理、壓縮堆空間。這樣就會有一個問題:經過CMS收集的堆會產生空間碎片。

-XX:+UseCMSCompactAtFullCollection:FULL GC後進行內存壓縮,該參數與-XX:CMSFullGCsBeforeCompaction配合使用,該參數默認爲true。
-XX:CMSFullGCsBeforeCompaction:FULL GC多少次後進行內存壓縮,CMS回退到full GC時用的算法是mark-sweep-compact(標記-清除-壓縮 也叫 標記-整理),但compaction是可選的,不做的話碎片化會嚴重些但這次full GC的暫停時間會短些,這是個取捨。
CMSFullGCsBeforeCompaction 說的是,在上一次CMS併發GC執行過後,到底還要再執行多少次full GC纔會做壓縮。默認是0,也就是在默認配置下每次CMS GC頂不住了而要轉入full GC的時候都會做壓縮。 把CMSFullGCsBeforeCompaction配置爲10,就會讓上面說的第一個條件變成每隔10次真正的full GC才做一次壓縮(而不是每10次CMS併發GC就做一次壓縮,目前VM裏沒有這樣的參數)。這會使full GC更少做壓縮,也就更容易使CMS的old gen受碎片化問題的困擾。 本來這個參數就是用來配置降低full GC壓縮的頻率,以期減少某些full GC的暫停時間。CMS回退到full GC時用的算法是mark-sweep-compact,但compaction是可選的,不做的話碎片化會嚴重些但這次full GC的暫停時間會短些;這是個取捨。儘量不要出現 CMS的FULL GC,如果出現FULL GC頻繁,優化不上去的話,可以考慮G1

  • 需要更多的CPU資源。從上面的圖可以看到,爲了讓應用程序不停頓,CMS線程和應用程序線程併發執行,這樣就需要有更多的CPU,單純靠線程切 換是不靠譜的。並且,重新標記階段,爲空保證STW快速完成,也要用到更多的甚至所有的CPU資源。時間與吞吐需要平衡`

  • CMS的另一個缺點是它需要更大的堆空間。因爲CMS標記階段應用程序的線程還是在執行的,那麼就會有堆空間繼續分配的情況,爲了保證在CMS回 收完堆之前還有空間分配給正在運行的應用程序,必須預留一部分空間。也就是說,CMS不會在老年代滿的時候纔開始收集。相反,它會嘗試更早的開始收集,已 避免上面提到的情況:在回收完成之前,堆沒有足夠空間分配!默認當老年代使用68%的時候,CMS就開始行動了。 – XX:CMSInitiatingOccupancyFraction =n 來設置這個閥值。

void ConcurrentMarkSweepGeneration::init_initiating_occupancy(intx io, uintx tr) {
  assert(io <= 100 && tr <= 100, "Check the arguments");
  if (io >= 0) {
    _initiating_occupancy = (double)io / 100.0;
  } else {
    _initiating_occupancy = ((100 - MinHeapFreeRatio) +
                             (double)(tr * MinHeapFreeRatio) / 100.0)
                            / 100.0;
  }
}

其中兩個參數,io就是CMSInitiatingOccupancyFraction的值,如果你沒設置過就是虛擬機自己的默認值,默認-1,可以用指令來查看:

java -XX:+PrintFlagsFinal -version |grep CMSInitiatingOccupancyFraction
     intx CMSInitiatingOccupancyFraction            = -1                                  {product}
java version "1.8.0_221"
Java(TM) SE Runtime Environment (build 1.8.0_221-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.221-b11, mixed mode)

tr就是JVM啓動參數CMSTriggerRatio的值,沒手動設置過的話也同樣也可以用指令來查看默認值:

 java -XX:+PrintFlagsFinal -version |grep CMSTriggerRatio
    uintx CMSTriggerRatio                           = 80                                  {product}
java version "1.8.0_221"
Java(TM) SE Runtime Environment (build 1.8.0_221-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.221-b11, mixed mode)

MinHeapFreeRatio

java -XX:+PrintFlagsFinal -version |grep MinHeapFreeRatio
    uintx MinHeapFreeRatio                          = 0                                   {manageable}
java version "1.8.0_221"
Java(TM) SE Runtime Environment (build 1.8.0_221-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.221-b11, mixed mode)

總結:
1、CMSInitiatingOccupancyFraction默認值是-1,並不是許多人說的68、92之類的。

2、CMSInitiatingOccupancyFraction是用來計算老年代最大使用率(_initiating_occupancy)的。大於等於0則直接取百分號,小於0則根據公式來計算。這個_initiating_occupancy需要配合-XX:+UseCMSInitiatingOccupancyOnly來使用。

3、不同版本最終得到的_initiating_occupancy不同,歸根結底應該是不同版本下的MinHeapFreeRatio值不同。

總得來說,CMS回收器減少了回收的停頓時間,但是降低了堆空間的利用率。

關鍵參數說明

CMS

  • -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$LOG_DIR/heap_dump_%p.log
  • -XX:MaxTenuringThreshold=15 年齡
  • -XX:+UseParNewGC
  • -XX:+UseConcMarkSweepGC
    -XX:CMSInitiatingOccupancyFraction=70 和
    -XX:+UseCMSInitiatingOccupancyOnly

-XX:CMSInitiatingOccupancyFraction=70 是指設定CMS在對內存佔用率達到70%的時候開始GC(因爲CMS會有浮動垃圾,所以一般都較早啓動GC);

-XX:+UseCMSInitiatingOccupancyOnly 只是用設定的回收閾值(上面指定的70%),如果不指定,JVM僅在第一次使用設定值,後續則自動調整.

-XX:+CMSScavengeBeforeRemark

在CMS GC前啓動一次ygc,目的在於減少old gen對ygc gen的引用,降低remark時的開銷-----一般CMS的GC耗時 80%都在remark階段

G1

官方參數說明
英文原文
https://www.oracle.com/technical-resources/articles/java/g1gc.html

中文文檔
https://www.oracle.com/cn/technical-resources/articles/java/g1gc.html

https://www.cnblogs.com/cuizhiquan/articles/11058497.html

G1 回收過程如下。

  • G1 的年輕代回收,採用複製算法,並行進行收集,收集過程會 STW。

  • G1 的老年代回收時也同時會對年輕代進行回收。主要分爲四個階段:

  1. 依然是初始標記階段完成對根對象的標記,這個過程是STW的;

  2. 併發標記階段,這個階段是和用戶線程並行執行的;

  3. 最終標記階段,完成三色標記週期;

  4. 複製/清除階段,這個階段會優先對可回收空間較大的 Region 進行回收,即 garbage first,這也是 G1 名稱的由來。

G1 採用每次只清理一部分而不是全部的 Region 的增量式清理,由此來保證每次 GC 停頓時間不會過長。

總結如下,G1 是邏輯分代不是物理劃分,需要知道回收的過程和停頓的階段。此外還需要知道,G1 算法允許通過 JVM 參數設置 Region 的大小,範圍是 1~32MB,可以設置期望的最大 GC 停頓時間等。有興趣讀者也可以對 CMS 和 G1 使用的三色標記算法做簡單瞭解。

參數配置

G1

在一次混合收集中可以回收多少分區,取決於三個因素:

  1. 有多少個分區被認定爲垃圾分區,-XX:G1MixedGCLiveThresholdPercent=n這個參數表示如果一個分區中的存活對象比例超過n,就不會被挑選爲垃圾分區
  2. G1在一個併發週期中,最多經歷幾次混合收集週期,這個可以通過-XX:G1MixedGCCountTarget=n設置,默認是8,如果減小這個值,可以增加每次混合收集收集的分區數,但是可能會導致停頓時間過長
  3. 期望的GC停頓的最大值,由MaxGCPauseMillis參數確定,默認值是200ms,在混合收集週期內的停頓時間是向上規整的,如果實際運行時間比這個參數小,那麼G1就能收集更多的分區。
參數及默認值 說明
-XX:G1HeapRegionSize = n 設置region的大小,該值爲2的冪,範圍爲1MB到32 MB,目標是根據最小Java堆大小,將堆分成大約2048個region。
-XX:MaxGCPauseMillis = 200 設置最大停頓時間,默認值爲200毫秒。
-XX:G1NewSizePercent = 5 設置年輕代佔整個堆的最小百分比,默認值是5,這是個實驗參數,如果設置該值,將覆蓋默認參數 -XX:DefaultMinNewGenPercent。(JVM build > 23)
-XX:G1MaxNewSizePercent = 60 設置年輕代佔整個堆的最大百分比,默認值是60,這是個實驗參數,如果設置該值,將覆蓋默認參數 -XX:DefaultMaxNewGenPercent。(JVM build > 23)
-XX:ParallelGCThreads = n 設置STW的垃圾收集線程數,當邏輯處理器數量小於8時,n的值與邏輯處理器數量相同;如果邏輯處理器數量大於8個,則n的值大約爲邏輯處理器數量的5/8,大多數情況下是這樣,除了較大的SPARC系統,其中n的值約爲邏輯處理器的5/16。
-XX:ConcGCThreads = n 設置並行標記線程的數量,設置n大約爲ParallelGCThreads參數值的1/4。
-XX:InitiatingHeapOccupancyPercent = 45 設置觸發標記週期的Java堆佔用閾值,默認值爲45。
-XX:G1MixedGCLiveThresholdPercent = 65 這個參數表示如果一個分區中的存活對象比例超過n,就不會被挑選爲垃圾分區(JVM build > 23)
-XX:G1HeapWastePercent=10 設置浪費的堆內存百分比,當可回收百分比小於浪費百分比時,JVM就不會啓動混合垃圾收。(就是設置垃圾對象佔用內存百分比的最大值)。(JVM build > 23)
-XX:G1MixedGCCountTarget = 8 設置在標記週期完成之後混合收集的數量,以維持old region(也就是老年代)中,最多有G1MixedGCLiveThresholdPercent的存活對象。默認值爲8,混合收集的數量將維持在這個值之內。(JVM build > 23)
-XX:G1OldCSetRegionThresholdPercent = 10 設置在一次混合收集中被收集的old region數量的上線,默認值是整個堆的10%。(JVM build > 23)
-XX:G1ReservePercent = 10 設置預留空閒內存百分比,以降低內存溢出的風險。默認值爲10%。增加或減少百分比時,請確保將總Java堆調整相同的量。(JVM build > 23)

注:表格中的 JVM build 23表示該參數只有在Java HotSpot VM build 大於 23時才能生效,小於或等於都不能生效

-XX:+InitiatingHeapOccupancyPercent(簡稱IHOP):G1內部並行循環啓動的設置值,默認爲Java Heap的45%。這個可以理解爲老年代空間佔用的空間,GC收集後需要低於45%的佔用率。這個值主要是爲了決定在什麼時間啓動老年代的並行回收循環,這個循環從初始化並行回收開始,可以避免Full GC的發生;

-XX:G1HeapWastePercent:G1不會回收的內存大小,默認是堆大小的10%。意思是允許整個堆內存中有10%的空間被浪費,意味着如果發現可以回收的垃圾佔堆內存的比例低於10%,則不再進行混合回收。因爲GC會花費很多的時間但是回收到的內存卻很少。

-XX:G1MixedGCLiveThresholdPercent,默認爲65%,意思是垃圾佔內存分段比例要達到65%纔會被回收。如果垃圾佔比太低,意味着存活的對象佔比高,在複製的時候會花費更多的時間。

G1的回收策略

1、新生代老年代是個邏輯的概念,即同一個Region在系統經歷過若干次垃圾回收後,做過新也當過老,也有可能新老都沒有作過
2、空間分配的單位是Region
3、雖然新老是個邏輯概念,但他們是有自己的大小上下限的。默認情況下新最大佔60%,老最大佔(1 - 5%的新生代初始值)=95%
4、觸發和回收新生代和之前的過程一樣。不同點在於:由於MaxGCPauseMills的設定,只會在指定時間內回收掉儘可能多的垃圾對象
5、進入老年代的條件:1、年齡大了 2、動態年齡判斷 3、存活對象Survivor放不下
6、大於單個Region 50%的屬於大對象

  • GC模式

G1中提供了三種模式垃圾回收模式,young gc、mixed gc 和 full gc,在不同的條件下被觸發。

  1. young gc
    發生在年輕代的GC算法,一般對象(除了巨型對象)都是在eden region中分配內存,當所有eden region被耗盡無法申請內存時,就會觸發一次young gc,這種觸發機制和之前的young gc差不多,執行完一次young gc,活躍對象會被拷貝到survivor region或者晉升到old region中,空閒的region會被放入空閒列表中,等待下次被使用。
  • mixed gc
    當越來越多的對象晉升到老年代old region時,爲了避免堆內存被耗盡,虛擬機會觸發一個混合的垃圾收集器,即mixed gc,該算法並不是一個old gc,除了回收整個young region,還會回收一部分的old region,這裏需要注意:是一部分老年代,而不是全部老年代,可以選擇哪些old region進行收集,從而可以對垃圾回收的耗時時間進行控制。

那麼mixed gc什麼時候被觸發?

先回顧一下cms的觸發機制,如果添加了以下參數:

-XX:CMSInitiatingOccupancyFraction=80
-XX:+UseCMSInitiatingOccupancyOnly
當老年代的使用率達到80%時,就會觸發一次cms gc。相對的,mixed gc中也有一個閾值參數-XX:InitiatingHeapOccupancyPercent,當老年代大小佔整個堆大小百分比達到該閾值時,會觸發一次mixed gc.

mixed gc的執行過程有點類似cms,主要分爲以下幾個步驟:

initial mark: 初始標記過程,整個過程STW,標記了從GC Root可達的對象
concurrent marking: 併發標記過程,整個過程gc collector線程與應用線程可以並行執行,標記出GC Root可達對象衍生出去的存活對象,並收集各個Region的存活對象信息
remark: 最終標記過程,整個過程STW,標記出那些在併發標記過程中遺漏的,或者內部引用發生變化的對象
clean up: 垃圾清除過程,如果發現一個Region中沒有存活對象,則把該Region加入到空閒列表中

  • full gc
    如果對象內存分配速度過快,mixed gc來不及回收,導致老年代被填滿,就會觸發一次full gc,G1的full gc算法就是單線程執行的serial old gc,會導致異常長時間的暫停時間,需要進行不斷的調優,儘可能的避免full gc.

UnlockExperimentalVMOptions

Error: VM option 'G1NewSizePercent' is experimental and must be enabled via -XX:+UnlockExperimentalVMOptions.
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.
XX:+UnlockExperimentalVMOptions -XX:G1NewSizePercent=30 -XX:G1MaxNewSizePercent=70

啥時候用G1

有實驗表明G1在回收Eden分區的時候,大概每GB需要100ms,如果GC回收時間超過500ms以上,可以考慮用G1!!
參考:
https://developer.aliyun.com/article/724633

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章