一文打尽Java垃圾回收器

垃圾回收器介绍

有 8 种不同的垃圾回收器,它们分别用于不同分代的垃圾清理

  • • 新生代(复制算法):Serial,ParNew,Parallel Scavenge
  • • 老年代(标记-清除、标记-整理):SerialOld,Parallel Old,CMS
  • • 整堆:G1,ZGC

下图是各种垃圾回收器之间的关系,连线表示相互可以配合使用。

一文打尽Java垃圾回收器_第1张图片

收集器 特点 介绍
Serial 新生代收集器
串行
标记-复制
在回收时,JVM会暂停所有用户线程,执行垃圾回收操作,直到操作完成后再继续用户线程。由于该垃圾回收器只使用一个线程,因此也被称为串行垃圾回收器。
Serial Old 老年代
串行
标记-整理
是Serial收集器的升级版,用于回收老年代内存。该垃圾回收器也只使用一个线程进行回收。
ParNew 新生代
多线程并行执行
“标记-复制”算法
是一种并行垃圾回收器,主要用于新生代内存的回收。该垃圾回收器使用多线程并行执行“标记-复制”算法。
Parallel Scavenge 新生代
多线程并行执行
“标记-复制”算法
是一种并行垃圾回收器,主要用于新生代内存的回收。它优化了垃圾回收的吞吐量,适用于那些重视系统吞吐量的应用场景。该垃圾回收器使用多线程并行执行“标记-复制”算法,并且可以动态调整回收时间,以适应不同的内存负载。
Parallel Old 使用多线程并行执行“标记-整理”算法
老年代
是一种并行垃圾回收器,用于回收老年代内存。该垃圾回收器使用多线程并行执行“标记-整理”算法。
CMS
Concurrent Mark Sweep)
使用多线程并行执行,低延时,减少STW对用户的影响
“标记-清除”算法
老年代
是一种混合垃圾回收器,主要用于回收老年代内存。该垃圾回收器使用多线程并行执行“标记-清除”算法,其中标记阶段是并发执行的,即在不停顿用户线程的情况下标记可回收对象,而清除阶段则会暂停用户线程。首次实现了让垃圾收集线程与用户线程(基本上)同时工作(并发收集)
G1
Garbage-First
使用多线程并行执行“标记-整理”算法
吞吐量和低延时都行的整堆垃圾收集器
可预测停顿
是一个横跨新生代和老年代的垃圾回收器。它打乱了以前的堆结构,直接将堆分成极其多个区域。每个区域都可以充当 Eden 区、Survivor 区或者老年代中的一个。它采用的是标记 - 压缩算法,而且和 CMS 一样都能够在应用程序运行过程中并发地进行垃圾回收。G1 能够针对每个细分的区域来进行垃圾回收。在选择进行垃圾回收的区域时,它会优先回收死亡对象较多的区域。这也是 G1 名字的由来。
zgc
Z Garbage Collector
使用多线程并行执行“标记-复制”算法
整堆
是一种可伸缩低延迟的垃圾回收器,在大多数情况下,垃圾收集的停顿时间不到10毫秒。与CMS中的ParNew和G1类似,ZGC也采用标记-复制算法,不过ZGC对该算法做了重大改进:ZGC在标记、转移和重定位阶段几乎都是并发的,这是ZGC实现停顿时间小于10ms目标的最关键原因。ZGC的设计思想是将Java堆分成一些小块,每个块都可以独立地进行垃圾回收。这样,在垃圾收集过程中,只需要回收一小部分的Java堆,从而实现短暂的停顿时间。同时,ZGC还支持动态地调整Java堆的大小,以便更好地适应应用程序的需求。

串行垃圾回收器Serial、SerialOld

单线程收集器,发展历史最悠久的收集器,当它在进行垃圾收集工作的时候,其他线程都必须暂停直到垃圾收集结束(Stop The World)。

虽然Serial收集器存在Stop The World的问题,但是在并行能力较弱的单CPU环境下往往表现优于其他收集器;因为它简单而高效,没有多余的线程交互开销;Serial对于运行在Client模式下的虚拟机来说是个很好的选择

使用-XX:+UseSerialGC参数可以设置这个Serial收集器

特点:

  • • Serial新生代收集器,单线程执行,使用复制算法
  • • SerialOld老年代收集器,单线程执行,使用标记-整理算法
  • • 进行垃圾收集时,必须暂停用户线程

一文打尽Java垃圾回收器_第2张图片

Parallel Scavenge

Parallel Scavenge收集器是采用复制算法的多线程新生代收集器,它与其他的收集器的不同之处在于它主要关心的是吞吐量,而其他的收集器关注的是尽可能的减少用户线程的等待时间(缩短Stop The World的时间)。

吞吐量=用户线程执行时间/(用户线程执行时间+垃圾收集时间),虚拟机总共运行100分钟,其中垃圾收集花费时间1分钟,那么吞吐量就是 99%

停顿时间越短适合需要和用户进行交互的程序,良好的响应能够提升用户的体验。而高效的吞吐量可以充分的利用CPU时间,尽快的完成计算任务,所以Parallel Scavenge收集器适用于后台计算型任务程序。

-XX:+UseParallelGC设置使用该回收器

特点:

  • • 吞吐量优先收集器,垃圾收集需要暂停用户线程
  • • 新生代使用并行回收器Parallel Scavenge,采用复制算法
  • • 老年代使用串行收集器Serial Old,采用标记-整理算法

一文打尽Java垃圾回收器_第3张图片

Parallel Old

Parallel Old收集器可以配合Parallel Scavenge收集器一起使用达到“吞吐量优先”,它主要是针对老年代的收集器,使用的是标记-整理算法。在注重吞吐量的任务中可以优先考虑使用这个组合。自 JDK 9 开始,Parallel Old 垃圾回收器已被 G1(Garbage-First)垃圾回收器取代为默认老年代回收器。

适用于吞吐量优先的应用场景,例如批处理任务、数据处理任务等。

-XX:+UseParallelOldGc 设置老年代使用该回收器。

XX:+ParallelGCThreads 设置垃圾收集时的线程数量。

特点:

  • • PS收集器的老年代版本
  • • 吞吐量优先收集器,垃圾收集需要暂停用户线程,对CPU敏感
  • • 老年代使用并行收集器Parallel Old,采用标记-整理算法
  • • 新生代使用并行回收器Parallel Scavenge,采用复制算法

一文打尽Java垃圾回收器_第4张图片

ParNew

ParNew收集器是Serial收集器的多线程版本;除了使用了多线程进行垃圾收集以外,其他的都和Serial一致;它默认开始的线程数与CPU的核数相同,可以通过参数-XX:ParallelGCThreads来设置线程数。

从最上面的图可以看出,能够与CMS配合使用的收集器,除了Serial以外,就只剩下ParNew,所以ParNew通常是运行在Server模式下的首选新生代垃圾收集器

使用·-XX:+UseParNewGC·参数可以设置新生代使用这个并行回收器

特点:

  • • 新生代采用并行回收器ParNew,Serial的多线程版。
  • • 老年代采用串行回收器SerialOld
  • • 单核CPU不建议使用

一文打尽Java垃圾回收器_第5张图片

CMS

cms介绍

CMS收集器是一种以获取最短回收停顿时间为目标的收集器,在互联网网站、B/S架构的中常用的收集器就是CMS,因为系统停顿的时间最短,给用户带来较好的体验。JDK 9 及以后的版本中已被标记为废弃(deprecated),JDK 14被移除。

适用于响应时间优先的应用场景,例如 Web 服务器、实时系统等,它们对于长时间的停顿非常敏感。

-XX:+UseConcMarkSweepGC设置老年代使用该回收器。

-XX:ConcGCThreads设置并发线程数量。

新生代默认ParNew 老年代:CMS

特点:

  • • 低延时,减少STW对用户的影响
  • • 并发收集,用户线程与收集线程一起执行,对CPU资源敏感
  • • 不会等待堆填满再收集,到达阈值就开始收集。
  • • 采用标记-清除算法,所以会产生内存碎片

gc过程

一文打尽Java垃圾回收器_第6张图片

  • • 01-初始标记阶段:会STW,标记出GCRoots可以关联到的对象,关联对象较少,所以很快
  • • 02-并发标记阶段:不会STW,遍历GCRoots直接对象的引用链,耗时长
  • • 03-重新标记阶段:会STW,修正并发标记期间的新对象记录
  • • 04-并发清除阶段:不会STW,清除垃圾对象,释放内存空间

优缺点

  • • 优点 CMS是一款优秀的收集器,它的主要优点:并发收集、低停顿,因此CMS收集器也被称为并发低停顿收集器(Concurrent Low Pause Collector)。
  • • 缺点
    • • CMS收集器对CPU资源非常敏感。 在并发阶段,它虽然不会导致用户线程停顿,但会因为占用了一部分线程(或者说CPU资源)而导致应用程序变慢,总吞吐量会降低。CMS默认启动的回收线程数是(CPU数量+3)/4,也就是当CPU在4个以上时,并发回收时垃圾收集线程不少于25%的CPU资源,并且随着CPU数量的增加而下降。但是当CPU不足4个时(比如2个),CMS对用户程序的影响就可能变得很大,如果本来CPU负载就比较大,还要分出一半的运算能力去执行收集器线程,就可能导致用户程序的执行速度忽然降低了50%,其实也让人无法接受。
    • • 无法处理浮动垃圾。 由于CMS并发清理阶段用户线程还在运行着,伴随程序运行自然就还会有新的垃圾不断产生。这一部分垃圾出现在标记过程之后,CMS无法再当次收集中处理掉它们,只好留待下一次GC时再清理掉。这一部分垃圾就被称为“浮动垃圾”。也是由于在垃圾收集阶段用户线程还需要运行,那也就还需要预留有足够的内存空间给用户线程使用,因此CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,回收阀值可以通过参数 -XX:CMSInitiatingoccupancyFraction来设置;如果回收阀值设置的太大,在CMS运行期间如果分配大的对象找不到足够的空间就会出现“Concurrent Mode Failure”失败,这时候会临时启动SerialOld GC来重新进行老年代的收集,这样的话停顿的时间就会加长。
    • • 标记-清除算法导致的空间碎片 CMS是一款基于“标记-清除”算法实现的收集器,这意味着收集结束时会有大量空间碎片产生。空间碎片过多时,将会给大对象分配带来很大问题,往往出现老年代空间剩余,但无法找到足够大连续空间来分配当前对象。为了解决这个问题CMS提供了一个参数 -XX:+UseCMSCompactAtFullCollecion,如果启用,在Full GC的时候开启内存碎片整理合并过程,由于内存碎片整理的过程无法并行执行,所以停顿的时间会加长。考虑到每次FullGC都要进行内存碎片合并不是很合适,所以CMS又提供了另一个参数 -XX:CMSFullGCsBeforeCompaction来控制执行多少次不带碎片整理的FullGC之后,来一次带碎片整理GC

G1

G1介绍

G1是一款面向服务端应用的全功能型垃圾收集器,大内存企业配置的主要是G1。

G1 算法取消了堆中年轻代与老年代的物理划分,但它仍然属于分代收集器。G1 算法将堆划分为若干个区域,称作 Region,如下图中的小方格所示。一部分区域用作年轻代,一部分用作老年代,另外还有一种专门用来存储巨型对象的分区。

一文打尽Java垃圾回收器_第7张图片

第31页-120.PNG

  • • 特点:
    • • G1 垃圾回收器使用的是分代垃圾回收算法,在执行垃圾回收时,会优先选择垃圾最多的区域进行回收(Garbage-First),以达到最大化回收效率的目标。
    • • 全局使用标记-整理算法收集,局部采用复制算法收集
    • • G1 垃圾回收器通过分代回收和智能化的区域选择,可以在大堆内存下降低停顿时间。能让使用者指定GC消耗时间,默认是200ms。
    • • G1最大堆内存32M2048=64GB,最小堆内存1M2048=2GB,低于此值不建议使用
    • • G1 垃圾回收器适用于中大型堆内存,具有较高吞吐量需求,同时对停顿时间有一定容忍度的应用场景。它适合于大多数的服务器应用和后端服务。
  • • 配置参数:-XX:+UseG1GC
  • • 配置说明

    一文打尽Java垃圾回收器_第8张图片

GC过程

一文打尽Java垃圾回收器_第9张图片

image.png

  • • G1 的年轻代回收,采用复制算法,并行进行收集,收集过程会 STW。
  • • G1 的老年代回收时也同时会对年轻代进行回收。主要分为四个阶段:
    • • 初始标记阶段:完成对根对象的标记,这个过程是STW的;标记出GCRoots可以关联到的对象,耗时短。
    • • 并发标记阶段:不会STW,这个阶段是和用户线程并行执行的;遍历GCRoots直接对象的引用链,耗时长。
    • • 最终标记阶段:会STW,完成三色标记周期,修正并发标记期间,标记产生变动的那部分。
    • • 复制/清除阶段:这个阶段会优先对可回收空间较大的 Region 进行回收,即 garbage first,这也是 G1 名称的由来。

G1 也和 CMS 一样会遍历全部的对象,然后标记对象引用情况,在清除对象后会对区域进行复制移动整合碎片空间。

G1 采用每次只清理一部分而不是全部的 Region 的增量式清理,由此来保证每次 GC 停顿时间不会过长。

标记阶段停顿分析

  • • 初始标记阶段:初始标记阶段是指从GC Roots出发标记全部直接子节点的过程,该阶段是STW的。由于GC Roots数量不多,通常该阶段耗时非常短。
  • • 并发标记阶段:并发标记阶段是指从GC Roots开始对堆中对象进行可达性分析,找出存活对象。该阶段是并发的,即应用线程和GC线程可以同时活动。并发标记耗时相对长很多,但因为不是STW,所以我们不太关心该阶段耗时的长短。
  • • 再标记阶段:重新标记那些在并发标记阶段发生变化的对象。该阶段是STW的。

清理阶段停顿分析

  • • 清理阶段清点出有存活对象的分区和没有存活对象的分区,该阶段不会清理垃圾对象,也不会执行存活对象的复制。该阶段是STW的。

复制阶段停顿分析

  • • 复制算法中的转移阶段需要分配新内存和复制对象的成员变量。转移阶段是STW的,其中内存分配通常耗时非常短,但对象成员变量的复制耗时有可能较长,这是因为复制耗时与存活对象数量与对象复杂度成正比。对象越复杂,复制耗时越长。

ZGC

ZGC介绍

ZGC ( Z Garbage Collector )在 JDK11中引入的一种可扩展的低延迟垃圾收集器,在 JDK15中发布稳定版。

ZGC 针对大堆内存设计可以支持 TB 级别的堆,ZGC 非常高效,能够做到 10ms 以下的回收停顿时间,JDK16发布后,GC停顿时间已经缩小到1ms以内。

  • • 配置参数
    -XX:+UseZGC  #启用ZGC
    -Xmx         # 设置最大堆内存
    -xlog:gc     # 打印 GC日志
    -Xlog:gc*    # 打印 GC 详细日志
    特点:
  • • ZGC 垃圾回收器使用的是整堆垃圾回收算法。它的设计目标是在非常大的堆内存下实现低延迟的垃圾回收。ZGC 将整个堆内存作为一个整体来进行回收
  • • 采用标记-整理算法
  • • ZGC 垃圾回收器的设计目标是实现非常低的停顿时间,通常控制在几毫秒到几十毫秒之间,无论堆内存大小。这使得 ZGC 更适合对延迟要求极高的应用场景,例如实时系统和交互性应用。
  • • 适合内存8MB~16TB
  • • ZGC 垃圾回收器适用于非常大的堆内存和对延迟敏感的应用场景,特别是那些需要低停顿时间的实时应用或交互性应用。

zgc 内存分布

跟 G1 类似,ZGC 的堆内存也是基于 Region 来分布,不过 ZGC 是不区分新生代老年代的。不同的是,ZGC 的 Region 支持动态地创建和销毁,并且 Region 的大小不是固定的,包括三种类型的 Region

image.png

  • • Small Region:2MB,主要用于放置小于 256 KB 的小对象。
  • • Medium Region:32MB,主要用于放置大于等于 256 KB 小于 4 MB 的对象。
  • • Large Region:N * 2MB。这个类型的 Region 是可以动态变化的,不过必须是 2MB 的整数倍,最小支持 4 MB。每个 Large Region 只放置一个大对象,并且是不会被重分配的。

zgc关键技术

这么快的响应,ZGC 是如何做到的呢?这是由于 ZGC 具有以下特点。

  • • ZGC 使用了着色指针技术,我们知道 64 位平台上,一个指针的可用位是 64 位,ZGC 限制最大支持 4TB 的堆,这样寻址只需要使用 42 位,那么剩下 22 位就可以用来保存额外的信息,着色指针技术就是利用指针的额外信息位,在指针上对对象做着色标记。
  • • 第二个特点是使用读屏障,ZGC 使用读屏障来解决 GC 线程和应用线程可能并发修改对象状态的问题,而不是简单粗暴的通过 STW 来进行全局的锁定。使用读屏障只会在单个对象的处理上有概率被减速。
  • • 由于读屏障的作用,进行垃圾回收的大部分时候都是不需要 STW 的,因此 ZGC 的大部分时间都是并发处理,也就是 ZGC 的第三个特点。
  • • 第四个特点是基于 Region,这与 G1 算法一样,不过虽然也分了 Region,但是并没有进行分代。ZGC 的 Region 不像 G1 那样是固定大小,而是动态地决定 Region 的大小,Region 可以动态创建和销毁。这样可以更好的对大对象进行分配管理。
  • • 第五个特点是压缩整理。CMS 算法清理对象时原地回收,会存在内存碎片问题。ZGC 和 G1 一样,也会在回收后对 Region 中的对象进行移动合并,解决了碎片问题。

读屏障是JVM向应用代码插入一小段代码的技术。当应用线程从堆中读取对象引用时,就会执行这段代码。需要注意的是,仅“从堆中读取对象引用”才会触发这段代码。

zgc过程

一文打尽Java垃圾回收器_第10张图片

image.png

  • • 开始进行回收时,ZGC 首先会进行一个短暂的 STW,来进行 roots 标记。这个步骤非常短,因为 roots 的总数通常比较小。
  • • 然后就开始进行并发标记,如上图所示,通过对对象指针进行着色来进行标记,结合读屏障解决单个对象的并发问题。其实,这个阶段在最后还是会有一个非常短的 STW 停顿,用来处理一些边缘情况,这个阶段绝大部分时间是并发进行的,所以没有明显标出这个停顿。
  • • 下一个是清理阶段,这个阶段会把标记为不在使用的对象进行回收,如上图所示,把橘色的不在使用的对象进行了回收。
  • • 最后一个阶段是重定位,重定位就是对 GC 后存活的对象进行移动,来释放大块的内存空间,解决碎片问题。
  • • 重定位最开始会有一个短暂的 STW,用来重定位集合中的 root 对象。暂停时间取决于 root 的数量、重定位集与对象的总活动集的比率。
  • • 最后是并发重定位,这个过程也是通过读屏障,与应用线程并发进行的

你可能感兴趣的:(java,算法,jvm)