GC回收算法及回收器实现

228 阅读20分钟

1、 GC 垃圾回收器

  • jdk1.6 默认垃圾收集器Parallel Scavenge(新生代【标记-复制算法】)+ Serial Old(老年代【标记整理算法】)

  • jdk1.7(7u4之前) 默认垃圾收集器其实还是Parallel Scavenge(新生代【标记-复制算法】)+ Serial Old(老年代【标记整理算法】)

  • HotSpot部分源码

    // hotspot\share\gc\parallel\parallelArguments.cpp
    void ParallelArguments::initialize() {
      GCArguments::initialize();
      assert(UseParallelGC || UseParallelOldGC, "Error");
      // Enable ParallelOld unless it was explicitly disabled (cmd line or rc file).
      if (FLAG_IS_DEFAULT(UseParallelOldGC)) {
        FLAG_SET_DEFAULT(UseParallelOldGC, true);
      }
      FLAG_SET_DEFAULT(UseParallelGC, true);
      ...
    }
    
  • 从jdk7u4开始,就对 “-XX:+UseParallelGC” 默认的老年代收集器进行了改进,使得HotSpot VM在选择使用 “-XX:+UseParallelGC” 时,会默认开启 " -XX:+UseParallelOldGC “,也就是说默认的老年代收集器是 Parallel Old,除非显式指定-XX:-UseParallelOldGC,否则都开启Parallel Old,即:默认垃圾收集器Parallel Scavenge(新生代【标记-复制算法】)+Parallel Old(老年代【标记整理算法】)

  • jdk1.8 默认垃圾收集器Parallel Scavenge(新生代【标记-复制算法】)+Parallel Old(老年代【标记整理算法】)

  • jdk1.9 默认垃圾收集器G1【从局部(两个Region之间)来看是基于"标记—复制"算法实现,从整体来看是基于"标记-整理"算法实现】

  • jdk11默认垃圾收集器为全新的垃圾收集器--ZGC

2、 回收器验证代码

import javax.management.ObjectName;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.util.Arrays;
import java.util.List; 

/**  
* @author yonghao.tang  
* @version 1.0.0  
* @ClassName GcCollectorPrinter.java  
* @Description TODO  
* @createTime 2022年12月02日 17:51:00  
*/
public class GcCollectorPrinter {  
    public static void main(String[] args) throws Exception {
        Object vmFlags = ManagementFactory.getPlatformMBeanServer().invoke(
                ObjectName.getInstance("com.sun.management:type=DiagnosticCommand"),
                "vmFlags",
                new Object[]{null},
                new String[]{"[Ljava.lang.String;"});

        //获取VM-GC参数
        for (String flag : ((String) vmFlags).split("\\s+")) {
            if (flag.contains("GC")) {
                System.out.println("VM参数:" + flag);
            }
        }
        //获取VM-GC收集器
        for (GarbageCollectorMXBean gc : ManagementFactory.getGarbageCollectorMXBeans()) {
            System.out.printf("%-20s%s%n", gc.getName(), Arrays.toString(gc.getMemoryPoolNames()));
        }
    }
}

3、 各个回收器介绍

JDK7后,HotSpot虚拟机中的7种垃圾收集器:Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1。

在这里插入图片描述

  • 新生代收集器:Serial、ParNew、Parallel Scavenge
  • 老年代收集器:Serial Old、Parallel Old、CMS
  • 整堆收集器:G1

回收器组合:

3.1 Serial收集器

Serial(串行)垃圾收集器是最基本、发展历史最悠久的收集器。JDK1.3.1前是HotSpot新生代收集的唯一选择。 第一代串行回收器,新生代和老年代都使用串行回收器,新生代使用复制算法,老年代使用标记-整理算法,Serial收集器是最基本、历史最悠久的收集器,它是一个单线程收集器。一旦回收器开始运行时,整个系统都要停止(Stop The World)。Client模式下默认开启,其他模式默认关闭

特点

  • 针对新生代
  • 采用复制算法
  • 单线程收集
  • 会出现GC停顿
  • Stop The World

应用场景

  • 依然是HotSpot在 Client 模式下默认的新生代收集器

设置参数:-XX:+UseSerialGC

GC组合:Serial(年轻代)+ Serial Old(老年代)

java -XX:+UseSerialGC -XX:+PrintGCDetails GcCollectorPrinter

VM参数:-XX:+PrintGCDetails
VM参数:-XX:+UseSerialGC

Copy                [Eden Space, Survivor Space]
MarkSweepCompact    [Eden Space, Survivor Space, Tenured Gen]

GC日志样例:
Heap
 def new generation   total 78016K, used 11139K [0x00000006c3000000, 0x00000006c84a0000, 0x0000000717550000)
  eden space 69376K,  16% used [0x00000006c3000000, 0x00000006c3ae0f20, 0x00000006c73c0000)
  from space 8640K,   0% used [0x00000006c73c0000, 0x00000006c73c0000, 0x00000006c7c30000)
  to   space 8640K,   0% used [0x00000006c7c30000, 0x00000006c7c30000, 0x00000006c84a0000)
 tenured generation   total 173440K, used 0K [0x0000000717550000, 0x0000000721eb0000, 0x00000007c0000000)
   the space 173440K,   0% used [0x0000000717550000, 0x0000000717550000, 0x0000000717550200, 0x0000000721eb0000)
 Metaspace       used 5732K, capacity 5846K, committed 6144K, reserved 1056768K
  class space    used 668K, capacity 686K, committed 768K, reserved 

3.2 Serial Old收集器

Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用“标记-整理”算法。

特点

  • 针对老年代
  • 采用 标记-整理 算法
  • 单线程收集
  • Stop The World

应用场景

  • 主要用于Client模式

  • Server模式有两大用途:

    • 1.在JDK1.5及之前,与Parallel Scavenge收集器搭配使用(JDK1.6有Parallel Old收集器可搭配)
    • 2.作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用

设置参数:-XX:+UseSerialOldGC (废弃,JVM不再支持)

3.3 ParNew收集器

ParNew垃圾收集器是 Serial 收集器的多线程版本。 除了使用多线程进行垃圾收集外,其余行为包括Serial收集器可用的所有控制参数、收集算法、Stop The World、对象分配规则、回收策略等与Serial收集器完全相同,两者共用了相当多的代码

特点

  • 针对新生代
  • 采用复制算法
  • 多线程收集
  • 会出现GC停顿
  • Stop The World

应用场景

  • 在Server模式下,ParNew 收集器是一个非常重要的收集器,因为除 Serial 外,只有它能与CMS收集器配合
  • 在单CPU环境中,不会比Serial收集器有更好的效果,因为存在线程交叉开销

设置参数:-XX:+UseParNewGC

-XX:+UseConcMarkSweepGC  指定使用CMS后会默认使用ParNew作为新生代收集器
-XX:+UseParNewGC  强制指定使用ParNew
-XX:ParallelGCThreads  指定垃圾收集的线程数量,ParNew默认开启的收集线程与CPU的数量相同

GC组合:ParNew(年轻代) + Serial Old(老年代)

java -XX:+UseParNewGC -XX:+PrintGCDetails GcCollectorPrinter

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.
将ParNew年轻收集器与 Serial 旧收集器一起使用已被弃用,可能会在未来的版本中删除。

VM参数:-XX:+UseParNewGC

ParNew              [Par Eden Space, Par Survivor Space]
MarkSweepCompact    [Par Eden Space, Par Survivor Space, Tenured Gen]

GC日志样例:
Heap
 par new generation   total 78016K, used 11139K [0x00000006c3000000, 0x00000006c84a0000, 0x0000000717550000)
  eden space 69376K,  16% used [0x00000006c3000000, 0x00000006c3ae0f20, 0x00000006c73c0000)
  from space 8640K,   0% used [0x00000006c73c0000, 0x00000006c73c0000, 0x00000006c7c30000)
  to   space 8640K,   0% used [0x00000006c7c30000, 0x00000006c7c30000, 0x00000006c84a0000)
 tenured generation   total 173440K, used 0K [0x0000000717550000, 0x0000000721eb0000, 0x00000007c0000000)
   the space 173440K,   0% used [0x0000000717550000, 0x0000000717550000, 0x0000000717550200, 0x0000000721eb0000)
 Metaspace       used 5721K, capacity 5846K, committed 6144K, reserved 1056768K
  class space    used 668K, capacity 686K, committed 768K, reserved 1048576K

3.4 Parallel Scavenge收集器

Parallel Scavenge收集器也是一个并行多线程新生代收集器。与其他收集器不同,Parallel Scavenge的目标是提高吞吐量,可以高效率地利用CPU时间,尽快完成程序的运算任务,适合在后台运算而不需要太多交互的任务。 Parallel Scavenge垃圾收集器因为与吞吐量关系密切,也称为吞吐量收集器(Throughput Collector)

特点

  • 针对新生代
  • 采用复制算法
  • 多线程收集
  • 高吞吐量为目标
  • Stop The World
  • 新生代和老年代都使用并行收集器。打印出的GC会带PSYoungGen、ParOldGen关键字

应用场景

  • 对暂停时间没有特别高的要求时,即程序主要在后台进行计算,不需要与用户进行太多交互
  • 例如:执行批量处理、订单处理、工资支付、科学计算等应用程序

设置参数:-XX:+UseParallelGC

-XX:+UseParallelGC
-XX:MaxGCPauseMillis  控制最大垃圾收集停顿时间,大于0的毫秒数
-XX:GCTimeRatio  设置垃圾收集时间占总时间比率  0 < n < 100的整数,相当于吞吐量大小
-XX:+UseAdptiveSizePolicy  开启这个参数后,就不用手工指定一些细节参数,如:-Xmn、-XX:SurvivorRation、-XX:PretenureSizeThreshold

GC组合:Parallel Scavenge(年轻代) + Parallel Old(老年代)

java -XX:+UseParallelGC -XX:+PrintGCDetails GcCollectorPrinter

VM参数:-XX:+PrintGCDetails
VM参数:-XX:+UseParallelGC

PS Scavenge         [PS Eden Space, PS Survivor Space]
PS MarkSweep        [PS Eden Space, PS Survivor Space, PS Old Gen]

GC日志样例:
Heap
 PSYoungGen      total 75776K, used 10414K [0x000000076bb00000, 0x0000000770f80000, 0x00000007c0000000)
  eden space 65024K, 16% used [0x000000076bb00000,0x000000076c52b810,0x000000076fa80000)
  from space 10752K, 0% used [0x0000000770500000,0x0000000770500000,0x0000000770f80000)
  to   space 10752K, 0% used [0x000000076fa80000,0x000000076fa80000,0x0000000770500000)
 ParOldGen       total 173568K, used 0K [0x00000006c3000000, 0x00000006cd980000, 0x000000076bb00000)
  object space 173568K, 0% used [0x00000006c3000000,0x00000006c3000000,0x00000006cd980000)
 Metaspace       used 5732K, capacity 5846K, committed 6144K, reserved 1056768K
  class space    used 668K, capacity 686K, committed 768K, reserved 1048576K

3.5 Parallel Old收集器

Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。

特点

  • 针对老年代
  • 采用 标记-整理 算法
  • 多线程收集
  • Stop The World
  • 新生代和老年代都使用并行收集器。打印出的GC会带PSYoungGen、ParOldGen关键字

应用场景

  • JDK1.6及之后用来替代老年代Serial Old收集器
  • 特别是在Server模式,多CPU的情况下

设置参数:-XX:+UseParallelOldGC

-XX:+UseParallelOldGC

GC组合:Parallel Scavenge(年轻代) + Parallel Old(老年代)

java -XX:+UseParallelOldGC -XX:+PrintGCDetails GcCollectorPrinter

VM参数:-XX:+PrintGCDetails
VM参数:-XX:+UseParallelOldGC

PS Scavenge         [PS Eden Space, PS Survivor Space]
PS MarkSweep        [PS Eden Space, PS Survivor Space, PS Old Gen]

GC日志样例:
Heap
 PSYoungGen      total 75776K, used 10414K [0x000000076bb00000, 0x0000000770f80000, 0x00000007c0000000)
  eden space 65024K, 16% used [0x000000076bb00000,0x000000076c52b810,0x000000076fa80000)
  from space 10752K, 0% used [0x0000000770500000,0x0000000770500000,0x0000000770f80000)
  to   space 10752K, 0% used [0x000000076fa80000,0x000000076fa80000,0x0000000770500000)
 ParOldGen       total 173568K, used 0K [0x00000006c3000000, 0x00000006cd980000, 0x000000076bb00000)
  object space 173568K, 0% used [0x00000006c3000000,0x00000006c3000000,0x00000006cd980000)
 Metaspace       used 5732K, capacity 5846K, committed 6144K, reserved 1056768K
  class space    used 668K, capacity 686K, committed 768K, reserved 1048576K
------------------------------------------------------------------

# 显式指定-XX:-UseParallelOldGC,关闭ParOldGen(Parallel Old),则默认启用PSOldGen(Serial Old)
java -XX:+UseParallelGC -XX:-UseParallelOldGC -XX:+PrintGCDetails GcCollectorPrinter

VM参数:-XX:+PrintGCDetails
VM参数:-XX:+UseParallelGC
VM参数:-XX:-UseParallelOldGC

PS Scavenge         [PS Eden Space, PS Survivor Space]
PS MarkSweep        [PS Eden Space, PS Survivor Space, PS Old Gen]

GC日志样例:
Heap
 PSYoungGen      total 75776K, used 10414K [0x000000076bb00000, 0x0000000770f80000, 0x00000007c0000000)
  eden space 65024K, 16% used [0x000000076bb00000,0x000000076c52b998,0x000000076fa80000)
  from space 10752K, 0% used [0x0000000770500000,0x0000000770500000,0x0000000770f80000)
  to   space 10752K, 0% used [0x000000076fa80000,0x000000076fa80000,0x0000000770500000)
 PSOldGen        total 173568K, used 0K [0x00000006c3000000, 0x00000006cd980000, 0x000000076bb00000)
  object space 173568K, 0% used [0x00000006c3000000,0x00000006c3000000,0x00000006cd980000)
 Metaspace       used 5722K, capacity 5846K, committed 6144K, reserved 1056768K
  class space    used 668K, capacity 686K, committed 768K, reserved 1048576K

3.6 CMS收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,它非常符合那些集中在互联网站或B/S系统的服务端上的JAVA应用,这些应用都非常重视服务的响应速度.

工作流程

  • 初始标记(CMS initial mark)
    • 仅标记一下GC Roots能直接关联到的对象;
    • 需要“Stop The World”,速度很快
  • 并发标记(CMS concurrent mark)
    • 进行GC Roots Tracing的过程;
    • 上一步产生的集合中标记出存活对象,并不能保证可以标记出所有的存活对象;
    • 应用程序也在运行,在整个过程中耗时最长
  • 重新标记(CMS remark)
    • 为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录;
    • 需要“Stop The World”,且停顿时间比初始标记阶段稍长,但远比并发标记的时间短;
    • 采用多线程并发执行来提升效率;
  • 并发清除(CMS concurrent sweep)
    • 回收所有的垃圾对象

耗时最长的并发标记和并发清除过程中,收集线程都可以与用户线程一起工作,不需要进行停顿

特点

  • 针对老年代
  • 采用 标记-清除 算法
  • 并发收集、低停顿
  • 以获取最短回收停顿时间为目的

应用场景

  • 与用户交互较多的场景
  • 希望系统停顿时间较短,注重服务的响应速度

设置参数:-XX:+UseConcMarkSweepGC

-XX:+UseConcMarkSweepGC

GC组合:ParNew(年轻代) + CMS(老年代)

java -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails GcCollectorPrinter

VM参数:-XX:+PrintGCDetails
VM参数:-XX:+UseConcMarkSweepGC
VM参数:-XX:+UseParNewGC

ParNew              [Par Eden Space, Par Survivor Space]
ConcurrentMarkSweep [Par Eden Space, Par Survivor Space, CMS Old Gen]

GC日志样例:
Heap
 par new generation   total 78016K, used 11139K [0x00000006c3000000, 0x00000006c84a0000, 0x00000006ec990000)
  eden space 69376K,  16% used [0x00000006c3000000, 0x00000006c3ae0f20, 0x00000006c73c0000)
  from space 8640K,   0% used [0x00000006c73c0000, 0x00000006c73c0000, 0x00000006c7c30000)
  to   space 8640K,   0% used [0x00000006c7c30000, 0x00000006c7c30000, 0x00000006c84a0000)
 concurrent mark-sweep generation total 173440K, used 0K [0x00000006ec990000, 0x00000006f72f0000, 0x00000007c0000000)
 Metaspace       used 5732K, capacity 5846K, committed 6144K, reserved 1056768K
  class space    used 668K, capacity 686K, committed 768K, reserved 1048576K

缺点

  • 对CPU资源非常敏感

    • 并发收集虽然不会暂停用户线程,但因为占用一部分CPU资源,还是会导致应用程序变慢,总吞吐量降低
    • CMS默认收集线程数量 = ( CPU数量 + 3 ) / 4
  • 无法处理浮动垃圾,可能出现“Concurrent Mode Failure”失败

    • 浮动垃圾(Floating Garbage)

      • 在并发清除时,用户线程新产生的垃圾称为浮动垃圾;这使得并发清除时需要预留一定的内存空间,不能像其他收集器在老年代几乎填满再进行收集
      • -XX:CMSInitiatingOccupancyFraction 设置CMS预留内存空间。JDK1.5默认68%,JDK1.6默认92%
    • Concurrent Mode Failure

      • 如果CMS预留内存空间无法满足程序需要,就会出现一次“Concurrent Mode Failure”失败
      • 此时JVM启用后备预案:临时启用Serial Old收集器,而导致另一次Full GC的产生
  • 产生大量内存碎片

    • 由于CMS基于“标记-清除”算法,清除后不进行压缩操作,因此产生大量不连续的内存碎片,有可能提前触发另一次Full GC动作
    • -XX:+UseCMSCompactAtFullCollection 使得CMS出现上面情况时不进行Full GC,而开启内存碎片的合并整理过程。默认开启
    • -XX:+CMSFullGCsBeforeCompaction 设置执行多少次不压缩的Full GC后,来一次压缩整理。默认0
    • 由于空间不再连续,CMS需要使用可用“空闲列表”内存分配方式

3.7 G1收集器

G1(Garbage-First)收集器是当今收集器技术发展最前沿的成果之一,是一款面向服务端应用的垃圾收集器。

工作流程

  • 初始标记(Initial Marking)
    • 仅标记一下GC Roots能直接关联到的对象;
    • 修改TAMS(Next Top at Mark Start),让下一阶段并发运行时,用户程序能在正确可用的Region中创建新对象;
    • 需要“Stop The World”,速度很快
  • 并发标记(Concurrent Marking)
    • 进行GC Roots Tracing的过程;
    • 上一步产生的集合中标记出存活对象,并不能保证可以标记出所有的存活对象;
    • 应用程序也在运行,在整个过程中耗时最长
  • 最终标记(Final Marking)
    • 为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录;
    • 上一阶段对象的变化记录在线程的Remembered Set Log;这里把Remembered Set Log合并到Remembered Set中
    • 需要“Stop The World”,且停顿时间比初始标记阶段稍长,但远比并发标记的时间短;
    • 采用多线程并发执行来提升效率;
  • 筛选回收(Live Data Counting And Evacuation)
    • 首先排序各个Region的回收价值和成本;
    • 根据用户期望的GC停顿时间来定制回收计划;
    • 按计划回收一些价值高的Region中垃圾对象;

回收时采用“复制”算法,从一个或多个Region复制存活对象到堆上的另一个空的Region,并且在此过程中压缩和释放内存

特点

  • 并行和并发
    • 能充分利用多CPU、多核环境下的硬件优势;
    • 可以并行来缩短STW停顿时间;
    • 也可以并发让垃圾收集和用户程序同时进行
  • 分代收集,收集范围包括新生代和老年代
    • 能独立管理整个GC堆(新生代&老年代),不需要与其他收集器搭配;
    • 能够采用不用方式处理不同时期的对象;
  • 结合多种垃圾收集算法,空间整合,不产生碎片
    • 整体看是基于“标记-整理算法”,局部(两个Region)看是基于复制算法
  • 可预测的停顿,低停顿的同时实现高吞吐量
    • G1除了追求低停顿,还能建立可预测的停顿时间模型

应用场景

  • 面向服务端应用,针对具有大内存、多处理器的机器
  • 最主要的应用是为需要低GC延迟,并具有大堆的应用程序提供解决方案

设置参数:-XX:+UseG1GC

-XX:+UseG1GC  指定使用G1收集器
-XX:InitiatingHeapOccupancyPercent  当整个Java堆的占用率达到参数值时,开始并发标记阶段,默认45
-XX:MaxGCPauseMillisG1设置暂停时间目标,默认200毫秒
-XX:G1HeapRegionSize  设置每个Region大小,范围1M~32M;目标是在最小Java堆时可以拥有约2048Region

GC组合:G1 Young(年轻代) + G1 Old(老年代)

java -XX:+UseG1GC -XX:+PrintGCDetails GcCollectorPrinter

VM参数:-XX:ConcGCThreads=2
VM参数:-XX:+PrintGCDetails
VM参数:-XX:+UseG1GC

G1 Young Generation [G1 Eden Space, G1 Survivor Space]
G1 Old Generation   [G1 Eden Space, G1 Survivor Space, G1 Old Gen]

GC日志样例:
Heap
 garbage-first heap   total 260096K, used 5120K [0x00000006c3000000, 0x00000006c31007f0, 0x00000007c0000000)
  region size 1024K, 6 young (6144K), 0 survivors (0K)
 Metaspace       used 5742K, capacity 5846K, committed 6144K, reserved 1056768K
  class space    used 668K, capacity 686K, committed 768K, reserved 1048576K