垃圾回收器

概述

垃圾收集器没有在规范中进行过多的规定,可以由不同的厂商、不同版本的JVM来实现。

由于JDK的版本处于高速迭代过程中,因此Java发展至今已经衍生了众多的GC版本。从不同角度分析垃圾收集器,可以将GC分为不同的类型。

垃圾回收器分类:

  • 线程数分:可以分为串行垃圾回收器和并行垃圾回收器。
  • 工作模式分:可以分为并发式垃圾回收器和独占式垃圾回收器。
    • 并发式垃圾回收器与应用程序线程交替工作,以尽可能减少应用程序的停顿时间。
    • 独占式垃圾回收器(Stop the world)一旦运行,就停止应用程序中的所有用户线程,直到垃圾回收过程完全结束。
  • 碎片处理方式分:可分为压缩式垃圾回收器和非压缩式垃圾回收器。
    • 压缩式垃圾回收器会在回收完成后,对存活对象进行压缩整理,消除回收后的碎片。
      • 再分配对象空间使用:指针碰撞
    • 非压缩式的垃圾回收器不进行这步操作。
      • 再分配对象空间使用:空闲列表
  • 工作的内存区间分,又可分为年轻代垃圾回收器和老年代垃圾回收器。

-XX:+PrinrCommandLineFlags:查看命令行相关参数(包含使用的垃圾收集器)

性能指标

  • 吞吐量:运行用户代码的时间占总运行时间的比例
    • (总运行时间:程序的运行时间+内存回收的时间)
  • 垃圾收集开销:吞吐量的补数,垃圾收集所用时间与总运行时间的比例。
  • 暂停时间:执行垃圾收集时,程序的工作线程被暂停的时间。
  • 收集频率:相对于应用程序的执行,收集操作发生的频率。
  • 内存占用:Java堆区所占的内存大小。
  • 快速:一个对象从诞生到被回收所经历的时间。

主要抓两点:

  • 吞吐量
  • 暂停时间

Serial回收器

Serial收集器是最基本、历史最悠久的垃圾收集器了。JDK1.3之前回收新生代唯一的选择。Serial 收集器采用复制算法、串行回收和”Stop-the-world”机制的方式执行内存回收。

serial old收集器同样也采用了串行回收和”Stop the world”机制,只不过内存回收算法使用的是标记-压缩算法。

  • serial old是运行在client模式下默认的老年代的垃圾回收器
  • Serial old在server模式下主要有两个用途:
    • 与新生代的ParallelScavenge配合使用
    • 作为老年代CMS收集器的后备垃圾收集方案

通过-XX:+UseSerialGC参数来设置使用该垃圾回收器

ParNew回收器

如果说Serial GC是年轻代中的单线程垃圾收集器,那么ParNew收集器则是serial收集器的多线程版本。但是只处理新生代。

ParNew收集器除了采用并行回收的方式执行内存回收外,两款垃圾收集器之间几乎没有任何区别。ParNew收集器在年轻代中同样也是采用复制算法、”Stop-the-world”机制。

ParNew是很多JVM运行在server模式下新生代的默认垃圾收集器。但是如果是单CPU并不适合。他还可以与CMS配合使用。

Parallel Scavenge回收器

HotSpot的年轻代中除了拥有ParNew收集器是基于并行回收的以外,Parallel Scavenge收集器同样也采用了复制算法、并行回收和”Stop the world”机制。

特点:

  • 和ParNew收集器不同,Parallel scavenge收集器的目标则是达到一个可控制的吞吐量(Throughput),它也被称为吞吐量优先的垃圾收集器。
  • 自适应调节策略也是Parallel scavenge与ParNew一个重要区别。

高吞吐量则可以高效率地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。因此,常见在服务器环境中使用。例如,那些执行批量处理、订单处理、工资支付、科学计算的应用程序。

CMS收集器

在JDK 1.5时期,HotSpot推出了一款在强交互应用中几乎可认为有划时代意义的垃圾收集器: CMS (concurrentrMark-Sweep)收集器,这款收集器是HotSpot虚拟机中第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程同时工作。

CMS收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间。停顿时间越短(低延迟)就越适合与用户交互的程序,良好的响应速度能提升用户体验。

CMS整个过程比之前的收集器要复杂,整个过程分为4个主要阶段:

  • 初始标记阶段
  • 并发标记阶段
  • 重新标记阶段
  • 并发清除阶段

缺点:

  • 会产生内存碎片
  • CMS收集器对CPU资源非常敏感
  • CMS收集器无法处理浮动垃圾。

CMS的垃圾收集算法采用标记-清除算法,并且也会”Stop-the-world”。配置-XX:+UseConcMarkSweepGC参数使用CMS垃圾收集器

G1回收器

G1 (Garbage-First)是一款面向服务端应用的垃圾收集器,主要针对配备多核CPU及大容量内存的机器,以极高概率满足GC停顿时间的同时,还兼具高吞吐量的性能特征

G1是一个并行回收器,它把堆内存**分割为很多不相关的区域(Region)**(物理上不连续的)。使用不同的Region来表示Eden、幸存者0区,幸存者1区,老年代等。

G1 GC有计划地避免在整个Java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的**价值大小(**回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。

记忆集

为了避免进行年轻代GC的时候需要对整个堆进行扫描,G1使用了记忆集来避免全局扫描。

每个Region都有一个对应的记忆集。每次引用类型写操作时,会产生一个写屏障暂时中断,判断指向的对象是否和这个引用为同一个Region,如果不是就会把该引用放入指向对象所在Region的记忆集中

当进行垃圾收集时,在GC根节点的枚举范围加入记忆集的遍历,就可以保证不进行全局扫描,也不会有遗漏。

G1收集器的特征:

  1. 主要关注吞吐量,即满足垃圾清理占用的时间在程序总运行时间中所占的比例足够小。

  2. 使用Region作为内存管理的单元

  3. 分代,即并非一次清理只面向某一个分代

    三个阶段:年轻代GC、老年代并发标记过程、混合回收

  4. 基于标记压缩算法,不会产生内存碎片

缺点:

  • G1无论是为了垃圾收集产生的内存占用(Footprint)还是程序运行时的额外执行负载(Overload)**都要比CMS要高。所以只有在大内存应用上才能发挥出优势。**

在JDK9之前使用需要配置-XX:+UseG1GC

小结

image-20230402100343123

  • 如果你想要最小化地使用内存和并行开销,请选Serial GC;
  • 如果你想要最大化应用程序的吞吐量,请选Parallel GC;
  • 如果你想要最小化GC的中断或停顿时间,请选CMS GC。

但是从JDK9到JDK17都默认使用的是G1回收器,后续的ZGC相当于是G1的优化版,响应速度更快。