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

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