JVM系列:二、不同 GC 的演变与差异

6 篇文章 0 订阅

上一篇 中,我们已经了解了 JVM 的各个分区及作用。本篇将介绍作用于 HeapSpace 和 MetaSpace 区域的 GC 。

GC 概览

  从 JVM 诞生到现在,不管什么收集器,都是基于三种基本 GC 算法 的组合。收集器发展到目前,分成了两派,分代收集和分区收集,其目的都只有一个,提高 JVM 的吞吐量,降低或控制应用停顿时间。

垃圾识别

既然要执行 GC ,那 JVM 肯定要使用一定的方法,找出垃圾对象,如:

1. 引用计数

  对每个对象的引用进行计数,每当有一个地方引用它时计数器 +1、引用失效则 -1,引用的计数放到对象头中,大于 0 的对象被认为是存活对象。

  虽然 循环引用的问题可通过 Recycler 算法解决,但是在多线程环境下,引用计数变更也要进行昂贵的同步操作,性能较低,早期的编程语言会采用此算法。

2.可达性算法

   从 GC Root 开始进行对象搜索,可以被搜索到的对象即为可达对象,此时还不足以判断对象是否存活/死亡,需要经过多次标记才能更加准确地确定,整个连通图之外的对象便可以作为垃圾被回收掉。目前 Java 中 主流的虚拟机均采用此算法

那么什么对象 适合作 GC Root 呢?有以下四种:

  • 程序运行时,虚拟机栈(为方法分配的帧栈中的局部变量表)中引用的对象
  • 方法区(jdk7及之前)/ 元空间(jdk8及以后) 内静态属性、常量引用的对象
  • 本地方法栈JNI(native方法)引用的对象

三种垃圾回收算法

  • Mark-Sweep(标记 - 清除):在 Heap 中,直接清除判定为垃圾的对象。该算法会带来内存碎片的问题,遇到大对象时,容易出现找不到连续可用内存空间的问题。

  • Copying(复制):GC 时将存活的对象,从一个区域复制到另外一个未使用的区。该算法将分配的内存再次分区,一定程度 上解决了内存碎片问题,但存在复制大对象时成本高的问题。

  • Mark-Compact(标记 - 整理):先通过 GC Root 标记存活对象,再对存活对象按照整理顺序(Compaction Order)进行整理。需要付出对象移动的代价

把 mark、sweep、compaction、copying 这几种动作的耗时放在一起看,大致有这样的关系:

在这里插入图片描述
compaction 耗时最长,因为该算法 可能要先计算一次对象的目标地址,然后修正指针,最后再移动对象,但其优点是不会产生内存碎片。是适用于 OldGen 的 GC。

用于 YoungGen 的 copy 算法:

内存空间比例:Eden:Survivor From:Survivor To=8:1:1

  1. 当 Eden 区满或对象的内存分配不足时,触发 Minor GC,将存活对象复制一个 Survivor区。Survivor 区域进行GC 和存活对象的来回复制,并将复制的次数记录到对象头信息中。

  2. 部分对象会在两个 Survivor 区来回复制,复制一定次数后,晋升到老年代。复制的次数会在程序运行时动态调整。

    • -XX:MaxTenuringThreshold=n: Sets the maximum tenuring threshold for use in adaptive GC sizing. The largest value is 15. The default value is 15 for the parallel (throughput) collector, and 6 for the CMS collector.
    • ==-XX:InitialTenuringThreshold=n:==最少n次复制后,可以晋升到老年代。默认为7

为什么我们需要两个 Survivor 区?

   复制算法中,使用两个 Survivor 区有两个目的,一是可以避免单个 Survivor 带来的内存碎片;二是为了对 TenuringThreshold 进行统计,准备晋升对象到 老年代。如果使用一个 Survivor 区,这两个目的实现更加复杂,具体细节可以自己想一下。

用于 OldGen 的 mark-compact 算法:

针对老年代对象存活率较高的对象回收,使用 标记-整理算法 ,将所有存活对象都向一端移动,然后直接清理掉端边界以外的内存

发生在老年代的GC称为Full GC,又称为Major GC,其经常会伴随至少一次的Minor GC(并非绝对,在Parallel Scavenge收集器中就有直接进行Full GC的策略选择过程)。Major GC的速度一般会比Minor GC慢10倍以上,故应尽量避免Full GC。


垃圾收集器

垃圾收集器是垃圾回收算法的具体实现,常见的 垃圾收集器 如下:

  • Serial:串行收集,使用一个线程进行垃圾回收,会暂停 JVM 中执行的用户线程,无法对外提供服务
  • Parallel:并行收集,使用多个线程进行垃圾回收,也会暂停 JVM 中执行的用户线程,对外暂停服务的时间变短
  • CMS(Concurrent Mark Sweep):并发收集,用户线程与 GC 线程可同时运行,对外不会暂停服务
  • G1(Garbage First):对堆内存分为不同的区域,并发的对每个区域进行垃圾回收,从 JDK 8 开始即 可以 使用,JDK 9 默认使用 这种方式。

(此处需要图片说明)

如何查看当前 JVM 默认的的 GC?

在控制台使用如下命令查看 JVM 配置的参数:

>java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=132413952 -XX:MaxHeapSize=2118623232 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC (“+”表示并行收集被激活。)
java version "1.8.0_144"
Java(TM) SE Runtime Environment (build 1.8.0_144-b01)
Java HotSpot(TM) 64-Bit Server VM (build 25.144-b01, mixed mode)

如何查看某一个 jar 是否使用到了 UseG1GC ?

F:\IDEA_Project\springBoot\SpringBootDemo>jps #查看jvm中的进程
38224 Jps
39136 Launcher
28644
38984 Main1

F:\IDEA_Project\springBoot\SpringBootDemo>jinfo -flag UseG1GC 38984
-XX:+UseG1GC 			#“+”表示 Main1 使用到了 UseG1GC 收集器

F:\IDEA_Project\springBoot\SpringBootDemo>jinfo -flag UseParallelGC 38984
-XX:-UseParallelGC		#“-”表示 Main1 没有使用 UseParallelGC 收集器

下图为 JDK 8 中的 GC 种类和适用的内存区域。8 中以分代 GC 为主,并引入了分区收集 G1 GC。

在这里插入图片描述

分代 收集器用例(环境:JDK 8)

用于测试的代码,相关 JVM 参数:

参数:
-Xms10m -Xmx10m -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+(垃圾收集器种类)
代码:
	public static void main(String[] args) {
        System.out.println("hi serial GC");
        //1MB
        int size=1*1024*1024;
        byte[][] bytes=new byte[1024][];
        for (int i = 0; i < 20; i++) {
            bytes[i]=new byte[size];
        }
    }

下面对该段代码设置不同的垃圾收集器进行测试,观察 GC 日志。

Serial + SerialOld

   Serial 收集器的特点是稳定,是一个单线程的垃圾收集收集器,会执行 STW(stop-the-word)暂停其他所有线程,等待垃圾收集结束。

  我们在使用参数 -XX:+UseSerialGC时,即告知 JVM 在 Yong Generation 中使用 Serial 方式进行 GC(使用复制算法),在 Tenured Generation 中使用 Serial Old 方式进行 GC(使用标记整理算法)。

如下垃圾收集日志,便是使用的 UseSerialGC 。

-XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseSerialGC 
[GC (Allocation Failure) [DefNew: 2752K->320K(3072K), 0.0023756 secs] 2752K->1179K(9920K), 0.0024418 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 3072K->319K(3072K), 0.0022960 secs] 3931K->1824K(9920K), 0.0023400 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
hi serial GC
[GC (Allocation Failure) [DefNew: 2624K->50K(3072K), 0.0023840 secs] 4129K->3920K(9920K), 0.0024102 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 2150K->49K(3072K), 0.0014084 secs] 6020K->5967K(9920K), 0.0014271 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 2141K->2141K(3072K), 0.0000138 secs][Tenured: 5918K->5918K(6848K), 0.0028538 secs] 8059K->8015K(9920K), [Metaspace: 3547K->3547K(1056768K)], 0.0029293 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [Tenured: 5918K->5887K(6848K), 0.0035693 secs] 8015K->7983K(9920K), [Metaspace: 3547K->3547K(1056768K)], 0.0036204 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 3072K, used 2253K [0x00000000ff600000, 0x00000000ff950000, 0x00000000ff950000)
  eden space 2752K,  81% used [0x00000000ff600000, 0x00000000ff833790, 0x00000000ff8b0000)
  from space 320K,   0% used [0x00000000ff8b0000, 0x00000000ff8b0000, 0x00000000ff900000)
  to   space 320K,   0% used [0x00000000ff900000, 0x00000000ff900000, 0x00000000ff950000)
 tenured generation   total 6848K, used 5887K [0x00000000ff950000, 0x0000000100000000, 0x0000000100000000)
   the space 6848K,  85% used [0x00000000ff950000, 0x00000000fff0fed8, 0x00000000fff10000, 0x0000000100000000)
 Metaspace       used 3584K, capacity 4500K, committed 4864K, reserved 1056768K
  class space    used 389K, capacity 392K, committed 512K, reserved 1048576K
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at JVM.GarbageCollection.main(GarbageCollection.java:19)

ParNew + SerialOld

   ParNew 是一个并行的多个线程的垃圾收集器,会执行 STW(stop-the-word)暂停其他所有线程,等待垃圾收集结束。

  我们在使用参数 -XX:+UseParNewGC时,即告知 JVM 在 Yong Generation 中使用 Parallel 方式进行 GC(使用复制算法),在 Tenured Generation 中使用 Serial Old 方式进行 GC(使用标记整理算法)。

可用参数
  -XX:ParallelGCThreads=Num:配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。默认值为处理器数目。

如下垃圾收集日志,使用的便是 UseParNewGC 。
该方式与 UseSerialGC 的区别是,垃圾收集时的线程数不一致。值得注意的是日志末尾的提示,告知该方式的收集在 JDK 8 及以后被 Depracated 标记。

-XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParNewGC 
[GC (Allocation Failure) [ParNew: 2752K->319K(3072K), 0.0009978 secs] 2752K->1204K(9920K), 0.0010476 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [ParNew: 3071K->320K(3072K), 0.0010187 secs] 3956K->1876K(9920K), 0.0010440 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
hi serial GC
[GC (Allocation Failure) [ParNew: 2564K->291K(3072K), 0.0016276 secs] 4120K->4214K(9920K), 0.0016511 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [ParNew: 2462K->75K(3072K), 0.0012649 secs] 6385K->6046K(9920K), 0.0012911 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [ParNew: 2180K->2180K(3072K), 0.0000129 secs][Tenured: 5970K->6656K(6848K), 0.0040956 secs] 8151K->7699K(9920K), [Metaspace: 3491K->3491K(1056768K)], 0.0041689 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[Full GC (Allocation Failure) [Tenured: 6656K->6656K(6848K), 0.0023813 secs] 8723K->8723K(9920K), [Metaspace: 3491K->3491K(1056768K)], 0.0024138 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [Tenured: 6656K->6560K(6848K), 0.0033044 secs] 8723K->8627K(9920K), [Metaspace: 3491K->3491K(1056768K)], 0.0033449 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 par new generation   total 3072K, used 2226K [0x00000000ff600000, 0x00000000ff950000, 0x00000000ff950000)
  eden space 2752K,  80% used [0x00000000ff600000, 0x00000000ff82cab0, 0x00000000ff8b0000)
  from space 320K,   0% used [0x00000000ff8b0000, 0x00000000ff8b0000, 0x00000000ff900000)
  to   space 320K,   0% used [0x00000000ff900000, 0x00000000ff900000, 0x00000000ff950000)
 tenured generation   total 6848K, used 6560K [0x00000000ff950000, 0x0000000100000000, 0x0000000100000000)
   the space 6848K,  95% used [0x00000000ff950000, 0x00000000fffb8390, 0x00000000fffb8400, 0x0000000100000000)
 Metaspace       used 3572K, capacity 4498K, committed 4864K, reserved 1056768K
  class space    used 387K, capacity 390K, committed 512K, reserved 1048576K
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at JVM.GarbageCollection.main(GarbageCollection.java:16)
Java HotSpot(TM) 64-Bit Server VM warning: Using the ParNew young collector with the Serial old collector is deprecated and will likely be removed in a future release

Parallel(Parallel Scanvenge) + Parallel Old :(JDK 8 默认使用的组合)

   Parallel 是一个并行的多个线程的垃圾收集器,会执行 STW(stop-the-word)暂停其他所有线程,等待垃圾收集结束。

   Parallel Scavenge 的目标是达到一个 可控的吞吐量,吞吐量=程序运行时间/(程序运行时间+GC时间)。Parallel Scavenge 提供了两个参数用以精确控制吞吐量,分别是用以控制最大 GC 停顿时间的 -XX:MaxGCPauseMillis 及直接控制吞吐量的参数 -XX:GCTimeRatio

  我们在使用参数 -XX:+UseParallelGC 或 -XX:+UseParallelOldGC 时,即告知 JVM 在 Yong Generation 中使用 Parallel Scavenge 方式进行 GC(使用复制算法),在 Tenured Generation 中也使用 ParallelOld 方式进行 GC(使用标记整理算法)。

-XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC 
[GC (Allocation Failure) [PSYoungGen: 2048K->496K(2560K)] 2048K->971K(9728K), 0.0012240 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 2544K->480K(2560K)] 3019K->1595K(9728K), 0.0013929 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
hi serial GC
[GC (Allocation Failure) [PSYoungGen: 2120K->480K(2560K)] 8356K->7058K(9728K), 0.0009738 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 480K->0K(2560K)] [ParOldGen: 6578K->6881K(7168K)] 7058K->6881K(9728K), [Metaspace: 3542K->3542K(1056768K)], 0.0113635 secs] [Times: user=0.03 sys=0.00, real=0.01 secs] 
[Full GC (Ergonomics) [PSYoungGen: 1088K->1024K(2560K)] [ParOldGen: 6881K->6427K(7168K)] 7970K->7451K(9728K), [Metaspace: 3543K->3543K(1056768K)], 0.0110307 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) --[PSYoungGen: 1024K->1024K(2560K)] 7451K->7451K(9728K), 0.0005160 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 1024K->1024K(2560K)] [ParOldGen: 6427K->6406K(7168K)] 7451K->7431K(9728K), [Metaspace: 3543K->3543K(1056768K)], 0.0104724 secs] [Times: user=0.03 sys=0.00, real=0.01 secs] 
Heap
 PSYoungGen      total 2560K, used 1117K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
  eden space 2048K, 54% used [0x00000000ffd00000,0x00000000ffe174a8,0x00000000fff00000)
  from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
  to   space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
 ParOldGen       total 7168K, used 6406K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
  object space 7168K, 89% used [0x00000000ff600000,0x00000000ffc41bd8,0x00000000ffd00000)
 Metaspace       used 3585K, capacity 4500K, committed 4864K, reserved 1056768K
  class space    used 389K, capacity 392K, committed 512K, reserved 1048576K
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at JVM.GarbageCollection.main(GarbageCollection.java:16)

日志解毒

ParNew + CMS(Concurrent Mark Sweep)

   Concurrent Mark Sweep 并发标记清除,其目标是 低停顿、与用户线程并行执行 的垃圾收集器,适用于与用户交互的多的 B/S 服务器。

  我们在使用参数 -XX:+UseConcMarkSweepGC 时,即告知 JVM 在 Yong Generation 中使用 Parallel New 方式进行 GC(使用复制算法),在 Tenured Generation 中使用 CMS 方式进行 GC(使用标记清除算法)。

在这里插入图片描述
CMS 执行分4步:

  1. Initial Mark(初始标记):快速的标记 GC Roots 能 直接关联到的 对象, 需要STW

  2. concurrent-mark(并发标记):进行 GC Roots 跟踪的过程,和用户线程一起工作。由前阶段标记过的对象出发,所有可到达的对象都在本阶段中标记。

  3. concurrent-preclean(并发预清理):扫描 Card Table 脏的区域和新晋升到老年代的对象等,减少 Final Remark 的工作量。

  4. concurrent-abortable-preclean(可中断的并发清理):和上一阶段的工作内容一致。

  5. Final Remark(重新标记):在 Concurrent Mark 期间,因用户线程的运行,会使 Initial Mark 标记对象发生变动。该阶段对变动的那一部分对象重新标记,为正式的垃圾清理做最后的准备。Remark 阶段 需要 STW

  6. Concurrent Sweep(并发清除):清除 GC Roots 不可达对象,和用户线程一起工作,最后对死亡对象进行清除

  7. concurrent-reset(并发重置):重置 CMS 相关数据结构

缺点
8. 由于 CMS 产生的线程也需要占用堆内存,所以,CMS 必须要在老年代空间用尽前完成垃圾回收,否则 CMS 回收失败,将采用 Serial Old 方式执行 GC,由此带来更长的 STW。
9. 由于老年代采用的是 Mark-Sweep 算法,所以一定需要内存碎片整理。可以通过 -XX:CMSFullGCsBeforeCompaction=0 来指定间隔多少次进行一次内存碎片整理,0为默认值。

-XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:MaxNewSize=3497984 -XX:MaxTenuringThreshold=6 -XX:NewSize=3497984 -XX:OldPLABSize=16 -XX:OldSize=6987776 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:-UseLargePagesIndividualAllocation -XX:+UseParNewGC 
[GC (Allocation Failure) [ParNew: 2748K->320K(3072K), 0.0029262 secs] 2748K->1194K(9920K), 0.0029924 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [ParNew: 3072K->320K(3072K), 0.0014933 secs] 3946K->1871K(9920K), 0.0015267 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
hi serial GC
[GC (Allocation Failure) [ParNew: 2668K->271K(3072K), 0.0019613 secs] 4220K->4138K(9920K), 0.0019933 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (CMS Initial Mark) [1 CMS-initial-mark: 3867K(6848K)] 5162K(9920K), 0.0001467 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-mark-start]
[GC (Allocation Failure) [ParNew: 2371K->118K(3072K), 0.0015342 secs] 6239K->6033K(9920K), 0.0015604 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [ParNew: 2209K->2209K(3072K), 0.0000173 secs][CMS[CMS-concurrent-mark: 0.001/0.003 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
 (concurrent mode failure): 5915K->6616K(6848K), 0.0050711 secs] 8125K->7696K(9920K), [Metaspace: 3549K->3549K(1056768K)], 0.0051453 secs] [Times: user=0.03 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [CMS: 6616K->6400K(6848K), 0.0042853 secs] 8775K->8504K(9920K), [Metaspace: 3551K->3551K(1056768K)], 0.0043284 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure) [CMS: 6400K->6380K(6848K), 0.0043484 secs] 8504K->8483K(9920K), [Metaspace: 3551K->3551K(1056768K)], 0.0043880 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
初始标记,STW
[GC (CMS Initial Mark) [1 CMS-initial-mark: 6380K(6848K)] 8483K(9920K), 0.0001427 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
并发标记
[CMS-concurrent-mark-start]
[CMS-concurrent-mark: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-preclean-start]
[CMS-concurrent-preclean: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-abortable-preclean-start]
[CMS-concurrent-abortable-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
重新标记,STW
[GC (CMS Final Remark) [YG occupancy: 2177 K (3072 K)][Rescan (parallel) , 0.0001182 secs][weak refs processing, 0.0000062 secs][class unloading, 0.0002791 secs][scrub symbol table, 0.0004622 secs][scrub string table, 0.0001111 secs][1 CMS-remark: 6380K(6848K)] 8558K(9920K), 0.0010524 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] 
并发清除
[CMS-concurrent-sweep-start]
[CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-reset-start]
[CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 par new generation   total 3072K, used 2232K [0x00000000ff600000, 0x00000000ff950000, 0x00000000ff950000)
  eden space 2752K,  81% used [0x00000000ff600000, 0x00000000ff82e320, 0x00000000ff8b0000)
  from space 320K,   0% used [0x00000000ff8b0000, 0x00000000ff8b0000, 0x00000000ff900000)
  to   space 320K,   0% used [0x00000000ff900000, 0x00000000ff900000, 0x00000000ff950000)
 concurrent mark-sweep generation total 6848K, used 6378K [0x00000000ff950000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 3587K, capacity 4500K, committed 4864K, reserved 1056768K
  class space    used 389K, capacity 392K, committed 512K, reserved 1048576K
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at JVM.GarbageCollection.main(GarbageCollection.java:16)

日志解毒

小总结:

  以上介绍的几种收集器,都是分代收集的思想,那么如何在不同的场景,选用不同的收集器呢?

  1. 追求大吞吐量,如后台数据清洗、定时作业任务等,使用 -XX:+UseParallelGC
  2. 需要快速响应,追求低停顿时间,如与用户交互相关的应用,使用 -XX:+UseConcMarkSweepGC

分区 收集器用例(环境:JDK 8)

G1(Garbage-First)(JDK 9 默认使用)

在这里插入图片描述

G1的垃圾回收是作用在 Region 和 Humongous 上的,所以我们先了解一下两个区域的相关的特性:

  1. 整体上 采用 标记-整理 算法,局部 是通过复制算法,没有内存碎片化问题。

  2. G1 将堆空间分为多个大小一样的 Region(区域化),Region 的大小可以在启动设置(-XX:G1HeapRegionSize=32M) ,范围在1~32M,且必须是2的幂。默认的将整堆划分为2048个分区,也是最大数量,所以 G1 能够支持的最大内存为 64G。

  3. Humongous区域,如果一个对象占用的空间超过了 Region 容量的 50%,那么他会被存放到 Humongous 区。如果一个 H 区装不下,那么 G1 就会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动 Full GC 。

  4. G1 不再从物理区域上区分年轻代和老年代;但从逻辑上来讲,每个 Region 可以按需在年轻代和老年代之间切换,它们不再是物理隔离,而是一部分 Region 的集合且不需要 Region 是连续的。所以依然会采用不同 GC 方式来处理不同的区域。

  5. STW 时间可控,添加了停顿预测机制,用户可以 指定最大停顿时间的期望值-XX:MaxGCPauseMillis=m,JVM 将尽量保证停顿小于m)

  6. 将堆空间分成若干大小相同的子区域,GC 时按照区域扫描即可,缩小了扫描范围

G1是如何进行垃圾回收的?
在这里插入图片描述
4步过程:

  1. 初始标记(initial-mark):标记所有的 GC Root 。需要 STW。
  2. 并发标记(concurrent-mark-start):进行GC Roots Tracing的过程,可通过 ==-XX:ConcGcThreads=n ==指定并发 GC 使用的线程数
  3. 最终标记(Finalize Marking):修正并发标记期间,因程序运行导致标记发生变化的那一部分对象。需要 STW。
  4. 筛选回收(cleanup ):根据时间来进行价值最大化的回收,完成堆内存的整理。需要 STW。
-XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseG1GC -XX:-UseLargePagesIndividualAllocation 
hi g1 GC
[GC pause (G1 Evacuation Pause) (young), 0.0025745 secs]
   [Parallel Time: 1.7 ms, GC Workers: 4]
      [GC Worker Start (ms): Min: 658.5, Avg: 658.5, Max: 658.5, Diff: 0.0]
      [Ext Root Scanning (ms): Min: 0.0, Avg: 0.2, Max: 0.3, Diff: 0.3, Sum: 0.9]
      [Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
         [Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0]
      [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Object Copy (ms): Min: 0.0, Avg: 0.9, Max: 1.3, Diff: 1.3, Sum: 3.7]
      [Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.1, Diff: 0.1, Sum: 0.1]
         [Termination Attempts: Min: 1, Avg: 1.8, Max: 3, Diff: 2, Sum: 7]
      [GC Worker Other (ms): Min: 0.0, Avg: 0.4, Max: 1.6, Diff: 1.6, Sum: 1.8]
      [GC Worker Total (ms): Min: 1.6, Avg: 1.6, Max: 1.7, Diff: 0.0, Sum: 6.6]
      [GC Worker End (ms): Min: 660.1, Avg: 660.1, Max: 660.1, Diff: 0.0]
   [Code Root Fixup: 0.0 ms]
   [Code Root Purge: 0.0 ms]
   [Clear CT: 0.1 ms]
   [Other: 0.8 ms]
      [Choose CSet: 0.0 ms]
      [Ref Proc: 0.6 ms]
      [Ref Enq: 0.0 ms]
      [Redirty Cards: 0.1 ms]
      [Humongous Register: 0.0 ms]
      [Humongous Reclaim: 0.0 ms]
      [Free CSet: 0.0 ms]
   [Eden: 6144.0K(6144.0K)->0.0B(5120.0K) Survivors: 0.0B->1024.0K Heap: 6144.0K(10.0M)->1968.0K(10.0M)]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 
		1.初始标记
[GC pause (G1 Humongous Allocation) (young) (initial-mark) (to-space exhausted), 0.0064014 secs]
   [Parallel Time: 4.7 ms, GC Workers: 4]
      [GC Worker Start (ms): Min: 665.6, Avg: 665.6, Max: 665.6, Diff: 0.0]
      [Ext Root Scanning (ms): Min: 1.2, Avg: 3.4, Max: 4.1, Diff: 2.9, Sum: 13.6]
      [Update RS (ms): Min: 0.1, Avg: 0.2, Max: 0.4, Diff: 0.2, Sum: 0.9]
         [Processed Buffers: Min: 3, Avg: 4.3, Max: 7, Diff: 4, Sum: 17]
      [Scan RS (ms): Min: 0.1, Avg: 0.1, Max: 0.1, Diff: 0.0, Sum: 0.4]
      [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Object Copy (ms): Min: 0.1, Avg: 0.9, Max: 3.1, Diff: 3.0, Sum: 3.6]
      [Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
         [Termination Attempts: Min: 1, Avg: 2.3, Max: 5, Diff: 4, Sum: 9]
      [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [GC Worker Total (ms): Min: 4.6, Avg: 4.6, Max: 4.7, Diff: 0.0, Sum: 18.5]
      [GC Worker End (ms): Min: 670.2, Avg: 670.2, Max: 670.2, Diff: 0.0]
   [Code Root Fixup: 0.0 ms]
   [Code Root Purge: 0.0 ms]
   [Clear CT: 0.1 ms]
   [Other: 1.6 ms]
      [Evacuation Failure: 1.4 ms]
      [Choose CSet: 0.0 ms]
      [Ref Proc: 0.1 ms]
      [Ref Enq: 0.0 ms]
      [Redirty Cards: 0.1 ms]
      [Humongous Register: 0.0 ms]
      [Humongous Reclaim: 0.0 ms]
      [Free CSet: 0.0 ms]
   [Eden: 4096.0K(5120.0K)->0.0B(1024.0K) Survivors: 1024.0K->0.0B Heap: 8342.7K(10.0M)->6587.9K(10.0M)]
 [Times: user=0.02 sys=0.00, real=0.01 secs] 
[GC concurrent-root-region-scan-start]
[GC concurrent-root-region-scan-end, 0.0000328 secs]
			2.并发标记
[GC concurrent-mark-start]
[GC concurrent-mark-end, 0.0019545 secs]
[GC pause (G1 Humongous Allocation) (young) (to-space exhausted), 0.0009880 secs]
   [Parallel Time: 0.6 ms, GC Workers: 4]
      [GC Worker Start (ms): Min: 674.8, Avg: 674.8, Max: 674.8, Diff: 0.0]
      [Ext Root Scanning (ms): Min: 0.2, Avg: 0.2, Max: 0.2, Diff: 0.0, Sum: 0.7]
      [Update RS (ms): Min: 0.3, Avg: 0.3, Max: 0.3, Diff: 0.0, Sum: 1.1]
         [Processed Buffers: Min: 2, Avg: 3.0, Max: 4, Diff: 2, Sum: 12]
      [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Object Copy (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
         [Termination Attempts: Min: 1, Avg: 1.0, Max: 1, Diff: 0, Sum: 4]
      [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [GC Worker Total (ms): Min: 0.5, Avg: 0.5, Max: 0.5, Diff: 0.0, Sum: 1.9]
      [GC Worker End (ms): Min: 675.3, Avg: 675.3, Max: 675.3, Diff: 0.0]
   [Code Root Fixup: 0.0 ms]
   [Code Root Purge: 0.0 ms]
   [Clear CT: 0.1 ms]
   [Other: 0.3 ms]
      [Evacuation Failure: 0.1 ms]
      [Choose CSet: 0.0 ms]
      [Ref Proc: 0.1 ms]
      [Ref Enq: 0.0 ms]
      [Redirty Cards: 0.1 ms]
      [Humongous Register: 0.0 ms]
      [Humongous Reclaim: 0.0 ms]
      [Free CSet: 0.0 ms]
   [Eden: 1024.0K(1024.0K)->0.0B(1024.0K) Survivors: 0.0B->0.0B Heap: 8075.6K(10.0M)->6607.7K(10.0M)]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 
 			3.再次、最终标记
[GC remark [Finalize Marking, 0.0001166 secs] [GC ref-proc, 0.0002599 secs] [Unloading, 0.0005383 secs], 0.0010536 secs]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 
 			4.筛选回收
[GC cleanup 8075K->8075K(10M), 0.0003422 secs]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC pause (G1 Evacuation Pause) (young), 0.0006871 secs]
   [Parallel Time: 0.4 ms, GC Workers: 4]
      [GC Worker Start (ms): Min: 678.0, Avg: 678.0, Max: 678.0, Diff: 0.0]
      [Ext Root Scanning (ms): Min: 0.2, Avg: 0.2, Max: 0.2, Diff: 0.0, Sum: 0.7]
      [Update RS (ms): Min: 0.1, Avg: 0.1, Max: 0.2, Diff: 0.0, Sum: 0.6]
         [Processed Buffers: Min: 2, Avg: 2.8, Max: 3, Diff: 1, Sum: 11]
      [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Object Copy (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]
         [Termination Attempts: Min: 1, Avg: 1.0, Max: 1, Diff: 0, Sum: 4]
      [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [GC Worker Total (ms): Min: 0.3, Avg: 0.3, Max: 0.4, Diff: 0.0, Sum: 1.4]
      [GC Worker End (ms): Min: 678.4, Avg: 678.4, Max: 678.4, Diff: 0.0]
   [Code Root Fixup: 0.0 ms]
   [Code Root Purge: 0.0 ms]
   [Clear CT: 0.1 ms]
   [Other: 0.2 ms]
      [Choose CSet: 0.0 ms]
      [Ref Proc: 0.1 ms]
      [Ref Enq: 0.0 ms]
      [Redirty Cards: 0.1 ms]
      [Humongous Register: 0.0 ms]
      [Humongous Reclaim: 0.0 ms]
      [Free CSet: 0.0 ms]
   [Eden: 0.0B(1024.0K)->0.0B(1024.0K) Survivors: 0.0B->0.0B Heap: 8075.7K(10.0M)->7341.7K(10.0M)]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC pause (G1 Humongous Allocation) (mixed) (to-space exhausted), 0.0006827 secs]
   [Parallel Time: 0.3 ms, GC Workers: 4]
      [GC Worker Start (ms): Min: 679.0, Avg: 679.0, Max: 679.0, Diff: 0.0]
      [Ext Root Scanning (ms): Min: 0.2, Avg: 0.2, Max: 0.2, Diff: 0.0, Sum: 0.7]
      [Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
         [Processed Buffers: Min: 0, Avg: 0.5, Max: 2, Diff: 2, Sum: 2]
      [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Object Copy (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]
      [Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
         [Termination Attempts: Min: 1, Avg: 1.3, Max: 2, Diff: 1, Sum: 5]
      [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [GC Worker Total (ms): Min: 0.2, Avg: 0.2, Max: 0.2, Diff: 0.0, Sum: 0.8]
      [GC Worker End (ms): Min: 679.2, Avg: 679.2, Max: 679.2, Diff: 0.0]
   [Code Root Fixup: 0.0 ms]
   [Code Root Purge: 0.0 ms]
   [Clear CT: 0.1 ms]
   [Other: 0.3 ms]
      [Evacuation Failure: 0.1 ms]
      [Choose CSet: 0.0 ms]
      [Ref Proc: 0.1 ms]
      [Ref Enq: 0.0 ms]
      [Redirty Cards: 0.1 ms]
      [Humongous Register: 0.0 ms]
      [Humongous Reclaim: 0.0 ms]
      [Free CSet: 0.0 ms]
   [Eden: 1024.0K(1024.0K)->0.0B(1024.0K) Survivors: 0.0B->0.0B Heap: 7382.2K(10.0M)->7341.7K(10.0M)]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Allocation Failure)  7341K->3533K(10M), 0.0047497 secs]
   [Eden: 0.0B(1024.0K)->0.0B(3072.0K) Survivors: 0.0B->0.0B Heap: 7341.7K(10.0M)->3533.6K(10.0M)], [Metaspace: 3579K->3579K(1056768K)]
 [Times: user=0.00 sys=0.00, real=0.01 secs] 

相比于 CMS 的优势:

  • Region 概念的提出,缩小了 GC 扫描范围,GC 的操作每个 Region 的时间变少,进而使得 GC pause 的时间变得可控。
  • CMS 是分代收集中作用在 Tenured Generation 的收集器,G1 是分区收集中作用在每个 Region 的收集器

G1 从 JDK8 中开始引入;在 JDK12 及以后,添加了 GC 释放后的空闲内存返换给操作系统的功能。


就目前来讲,ZGC 和 Shenandoah 都还不稳定、完善,在此先了解一些基础概念,不做详细的介绍。

ZGC(The Z Garbage Collector)

  适用于大内存 低延迟服务的内存管理和回收,SPECjbb 2015 基准测试,在 128G 的大堆下,最大停顿时间才 1.68 ms,停顿时间远胜于 G1 和 CMS。

设计目标包括:

  • 停顿时间不超过10ms;

  • 停顿时间不会随着堆的大小,或者活跃对象的大小而增加;

  • 支持8MB~4TB级别的堆(未来支持16TB)

ZGC 在 JDK11 中推出 Experimental 版本,在 JDK13 中优化,添加了 GC 释放后的空闲内存返换给操作系统的功能。

美团:ZGC的探索与实践

Shenandoah(Red Hat研发)

  对 G1 的升级,引入 brooks pointers 将 G1 中需要 STW 的过程去除了。对于这种低延迟的保证,是以消耗 CPU 资源为代价的。

引入的 JDK 版本:
在这里插入图片描述

下图为与 CMS 和 G1 等收集器的 Latency by percentile distribution(按百分比发布的延迟):
在这里插入图片描述

GC 日志解毒

   不同的 GC 收集器中,打印的日志略有差异,我们看懂这些 GC Detail,才能判断内存使用有没有问题。

-XX:+UseParallelGC

[GC (Allocation Failure) [PSYoungGen: 2048K->496K(2560K)] 2048K->971K(9728K), 0.0012240 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 480K->0K(2560K)] [ParOldGen: 6578K->6881K(7168K)] 7058K->6881K(9728K), [Metaspace: 3542K->3542K(1056768K)], 0.0113635 secs] [Times: user=0.03 sys=0.00, real=0.01 secs] 
  • GC 类型: GC 表示是一次YGC(Young GC)、Full GC 表示是一次老年代 GC

  • GC Cause:导致本次 GC 发生的原因,下面是几个常见的 cause

    • Allocation Failure :内存分配失败
    • Metadata GC Threshold: Metaspace 空间不足导致的 GC
    • System.gc : 程序调用的 GC
    • Ergonomics:负责自动调解gc暂停时间和吞吐量之间的平衡,提高虚拟机性能更好的一种做法。
    • Promotion Failure: Old 区没有足够的空间分配给 Young 区晋升的对象(即使总可用内存足够大)。
    • Concurrent Mode Failure: CMS GC 运行期间,Old 区预留的空间不足以分配给新的对象,此时收集器会发生退化,严重影响 GC 性能
    • GCLocker Initiated GC: 如果线程执行在 JNI 临界区时,刚好需要进行 GC,此时 GC Locker 将会阻止 GC 的发生,同时阻止其他线程进入 JNI 临界区,直到最后一个线程退出临界区时触发一次 GC。
  • GC 的位置和空间大小变化:

    • [PSYoungGen: 2048K->496K(2560K)] 2048K->971K(9728K), 0.0012240 secs],表示本次 GC 后 Young Gen 最大为 2560K,空间占用从 2048K 变为 496K;Heap 最大为 9728K,空间从 2048K 变为 971K,耗时 0.0012240 secs。
  • 各阶段耗时统计:

    • [Times: user=0.02 sys=0.01, real=0.00 secs] 分别表示,用户态占用时长,内核用时,真实用时。

-XX:+UseConcMarkSweepGC

[Full GC (Allocation Failure) [CMS: 6400K->6380K(6848K), 0.0043484 secs] 8504K->8483K(9920K), [Metaspace: 3551K->3551K(1056768K)], 0.0043880 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (CMS Initial Mark) [1 CMS-initial-mark: 6380K(6848K)] 8483K(9920K), 0.0001427 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-mark-start]
[CMS-concurrent-mark: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-preclean-start]
[CMS-concurrent-preclean: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-abortable-preclean-start]
[CMS-concurrent-abortable-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (CMS Final Remark) [YG occupancy: 2177 K (3072 K)][Rescan (parallel) , 0.0001182 secs][weak refs processing, 0.0000062 secs][class unloading, 0.0002791 secs][scrub symbol table, 0.0004622 secs][scrub string table, 0.0001111 secs][1 CMS-remark: 6380K(6848K)] 8558K(9920K), 0.0010524 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] 
[CMS-concurrent-sweep-start]
[CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-reset-start]
[CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
  • 第 2 行:开始初始标记(需 STW)
    Old Gen 为共 6848K, 在使用到 6380K 时开始初始化标记,Heap 占用为 8483K,共 9920K,本次 CMS Initial Mark 耗时 0.0001427 secs。
  • 第 3-4 行:并发标记阶段开始和耗时
    目的:从GC Root 开始对堆中对象进行可达性分析,找出存活的对象
  • 第 5-6 行:并发预清理阶段开始和耗时
    目的:查找在执行并发标记阶段新进入老年代的对象,减少下一个阶段 Final Remark 的工作
  • 第 7-8 行:可中断的并发预清理阶段开始和耗时
  • 第 9 行:最终标记(需STW)
    目的:完成标记整个 OldGen 的所有的存活对象
    本次CMS GC 后:YoungGen 占用 2177K,共 3072K;并发扫描耗费 0.0001182 secs;弱引用处理、垃圾类卸载、符号引用表擦洗、字符串引用表擦洗耗时;OldGen 占用 6380K,共 6848K, Heap 占用 8558K,共 9920K;
  • 第 10-11 行:并发整理阶段开始与耗时
  • 第 12-13 行:并发重置阶段与耗时
    目的:完成 CMS 相关数据结构的初始化,方便下一次 CMS 执行。

GC 常见问题

不同 GC 的触发条件

Young GC 触发条件

   在 Eden 区为对象或 TLAB 分配内存失败,将导致一次 Young GC

CMS GC 触发的几个条件

CMS GC 在实现上分成 foreground collector 和 background collector。

1. foreground collector

CMS 收集器退化后的单线程串行 GC 模式,需要 STW 且时间长,需要格外注意。foreground collector 的 GC 算法有两种:

  1. 带压缩动作的算法,称为 MSC(Mark-Sweep-Compact),单线程全暂停的方式,对 Heap + Metaspace 进行垃圾收集,也就是真正意义上的 Full GC。
  2. 不带压缩动作的算法,收集 Old 区,暂停时间相对 MSC 算法短一些。

CMS 为了解决不带压缩动作算法产生的内存碎片问题,所以在每次进行 foreground collector 之前,会判断采用 MSC GC 还是 MS GC。

以上两种算法是在 JDK8 中讨论的,在 JDK 9 及以后的版本中,彻底去掉了 CMS forground collector 的功能,也就是说除了 background collector,就是压缩式的 Full GC。所以 UseCMSCompactAtFullCollection、CMSFullGCsBeforeCompaction 这两个参数也已经不再支持了。

CMS何时进行FullGC源码

2. background collector

   background collector 由 CMS 后台线程间隔一定的时间(CMSWaitDuration=2000ms,默认2s,运行时可修改),去扫描,然后决定是否触发。
这其中,有5中情况会触发 background collector:

  1. GC cause 是 _gc_locker 且开启了 GCLockerInvokesConcurrent (默认false);或者 GC cause 是_java_lang_system_gc(就是 System.gc()调用)且开启了 ExplicitGCInvokesConcurrent(默认false),那么将触发一次 background collector。

  2. 未开启 UseCMSInitiatingOccupancyOnly (默认false)情况下,根据统计数据动态计算,逻辑:
    如果预测 CMS GC 完成所需要的时间大于预计的老年代将要填满的时间,则进行 background collector。但在 JVM 启动初期,没有历史数据,所以默认情况下,OldGen 占比到 50% 就进行了一次 background collector

  3. 根据 OldGen 使用情况判断
    a. Old Gen 空间使用占比情况与阈值比较,如果大于阈值则进行 CMS GC,由 CMSInitiatingOccupancyFraction 控制阈值,默认92%
    b. 未开启 UseCMSInitiatingOccupancyOnly 情况下,Old Gen 进行了扩容,也会触发一次 background collector

  4. 根据增量 GC 是否可能会失败(悲观策略):计算 Young GC 晋升是否安全
    通过判断当前 Old Gen 剩余的空间大小是否足够容纳 Young GC 晋升的对象大小。 Young GC 到底要晋升多少是无法提前知道的,因此,这里通过统计平均每次 Young GC 晋升的大小和当前 Young GC 可能晋升的最大大小来进行比较

  5. Metaspace 进行扩容时,触发一次 background collector

更多源码细节参考

触发 Full GC 的几个场景

  1. OldGen 空间不足
  2. MetaSpace 空间不足
  3. 程序调用了 System.gc()
  4. 在要进行 young gc 的时候,根据之前统计数据发现 年轻代平均晋升大小 比现在 老年代剩余空间要大 ,那就会触发 full gc
  5. 待确定:堆中分配很大的对象
    • 所谓大对象,是指需要大量连续内存空间的java对象,例如很长的数组,此种对象会直接进入老年代,而老年代虽然有很大的剩余空间,但是无法找到足够大的连续空间来分配给当前对象,此种情况就会触发JVM进行Full GC。


  

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值