垃圾回收器

1、GC分类与性能指标

1.1 垃圾回收器的分类

GC可以是动词(Garbage Collection)垃圾收集,也可以是名词(Garbage Collector)垃圾回收器,视具体使用环境决定,垃圾收集器没有在规范中进行过多的规定,可以由不同的厂商、不同版本的JVM来实现。由于JDK的版本处于高速迭代过程中,因此Java发展至今已经衍生了众多的GC版本,从不同角度分析垃圾收集器,可以将GC分为不同的类型

1.1.1 按线程数分

按照线程数来分,可以分为串行垃圾回收器并行垃圾回收器

蓝色是用户线程,黄色是垃圾回收线程

在这里插入图片描述

串行回收指的是同一时间段内只允许有一个CPU用于执行垃圾回收操作,此时工作线程被暂停,直到垃圾收集工作结束。

并行收集可以运用多个CPU同时执行垃圾回收,因此提升了应用的吞吐量,不过并行回收仍然于串行回收一样,采用独占式,使用了“Stop-the-world”机制

  • 在单CPU处理器或者较小的应用等硬件平台不是特别优越的场合,串行回收器的性能表现可以超过并发回收器和并发回收器。所以,串行回收默认被应用在客户端的Client模式下的JVM中
  • 在并发能力比较强的CPU上,并行回收器产生的停顿时间要短于串行回收器

1.1.2 按工作模式分

按照工作模式分,可以分为并发式垃圾回收器独占式垃圾回收器

  • 并发式垃圾回收器与应用程序线程交替工作,以尽可能减少应用程序的停顿时间
  • 独占式垃圾回收器(Stop the world)一旦运行,就停止应用程序中的所有用户线程,直到垃圾回收过程完全结束。

在这里插入图片描述

1.1.3 按碎片处理方式分

按照碎片处理方式分,可分为压缩式垃圾回收器非压缩式垃圾回收器

  • 压缩式垃圾回收器在回收完后,对存活对象进行压缩整理,消除回收后的碎片
  • 非压缩式的垃圾回收器不进行这部操作

1.1.4 按工作的内存区间分

按工作的内存区间分,可分为年轻代垃圾回收器老年代垃圾回收器

1.2 评估GC的性能指标

  1. 吞吐量:运行用户代码的时间占总运行时间的比例

    (总运行时间 = 程序运行时间 + 垃圾回收时间)

  2. 垃圾收集开销:吞吐量的补数,垃圾收集所用的时间与总运行时间的比例

  3. 暂停时间:执行垃圾收集时,程序的工作线程被暂停的时间

  4. 收集频率:相对于应用的执行,收集操作发生的频率

  5. 内存占用:Java堆区所占的内存大小

  6. 快速:一个对象从诞生到被回收所经历的时间

吞吐量、暂停时间、内存占用是最重要的三项指标,这三者构成一个“不可能三角”,任何一款垃圾收集器不可能全部满足

这三项中,延迟的重要性日益凸显,因为随着硬件发展,内存占用多些也越来越能容忍,硬件性能的提升也有助于降低垃圾收集器运行时对用户线程的影响,即能提高吞吐量,但内存的扩大,反而让进行GC时的延迟更长

所以在设计垃圾回收器时,现在主要考虑两点:吞吐量暂停时间

1.3 吞吐量vs暂停时间

  • 吞吐量: CPU运行用户程序的时间占CPU总消耗时间的比值,吞吐量越高越好

    • 吞吐量 = 运行用户程序时间 / (运行用户程序时间 + 垃圾收集时间)
    • 高吞吐量的应用会让用户感觉程序一直在工作,在进行“生产”
  • 延迟进行GC的时间内,用户线程停止工作,这段时间就是延迟

    • 延迟越低越好,低延迟的应用让用户有流畅的交互体验

高吞吐和低延迟是矛盾的,因为:

  1. 如果要求程序有高吞吐量,那么必须降低GC的频率,但是这会导致单次GC使用更多的时间,导致延迟更长
  2. 如果要求程序低延迟,就需要提高GC的频率,单次GC的时间变短,延迟降低,但是这会导致整个GC占用更多的时间,导致程序吞吐量降低。

在这里插入图片描述

在这里插入图片描述

现代回收器标准:在最大吞吐量优先的情况下,降低延迟时间

2、不同的垃圾回收器概述

2.1 垃圾回收器的发展史

有了JVM,就一定需要垃圾收集的机制,即Garbage Collection,对应的产品称为Garbage Collector

  1. 1991随JDK1.3.1一起发布的串行收集器 Serial GC 是第一款GC.ParNew垃圾收集器是Serial收集器的多线程版本,一款并行回收器

  2. 2002 Parallel GCCMS GC跟随JDK1.4.2发布

  3. Parallel GC在JDK1.6之后成为HotSpot VM的默认GC

  4. 2012,自JDK1.7u4版本中,G1可用

  5. 2017,JDK9中G1成为默认的垃圾收集器,以替换CMS

  6. 2018,JDK10中G1有了并行的完整垃圾回收

  7. 2018,JDK11,引入Epsilon回收器 同时引入ZGC(可伸缩的低延迟垃圾回收器) 暂未实装

  8. 2019,JDK12中增强了G1 引入了Shenandoah GC(低延迟的GC)

  9. 2019,JDK13增强了ZGC 可以自动返回未用堆内存给操作系统

  10. 2020,JDK14删除了CMS 扩展了ZGC在macOS和Windows上的应用

七款经典的垃圾收集器:

  • 串行回收器:Serial、Serial Old
  • 并行回收器:ParNew、Parallel Scavenge、Parallel Old
  • 并发回收器:CMS、G1

2.2 经典回收器的回收区域

在这里插入图片描述

  • 年轻代回收器: Serial, ParNew, Parallel Scavenge

  • 老年代收集器: Serial Old, Parallel Old, CMS

  • 整堆收集器: G1

2.3 垃圾收集器的组合关系

在这里插入图片描述

实线: 表明二者可以搭配使用

红色虚线: JDK8时被废弃,JDK9时被移除

绿色虚线: JDK14时被弃用

青色虚线: CMS在JDK14中被移除

为什么要有这么多的垃圾回收器,一个不够用吗?

因为Java的使用场景很多,移动端,服务器端等,所以需要对不同的场景,提供不同的垃圾回收器,以提高不同场景下垃圾回收的性能,没有完美的垃圾回收器,但有某种场景下最适合的垃圾收集器

// 查看默认的垃圾收集器
-XX:+PrintComandLineFlags

3、Serial回收器:串行回收

// 使用参数
-XX:+UseSerialGC
//指定JVM年轻代使用SerialGC,老年代使用Serial Old GC
//这两个串行收集器,只要指定一个,另外一个自动使用
  1. Serial回收器是最基本,历史最长的垃圾回收器,在JDK1.3之前回收年轻代的唯一选择

  2. Serial回收器作为HotSpot中Client模式下的默认年轻代垃圾回收器

  3. Serial回收器采用复制算法串行回收STW的机制年轻代进行内存回收

  4. 除了年轻代外,Serial回收器还有专用于回收老年代垃圾Serial Old回收器, Serial Old使用标记-压缩算法串行回收以及STW机制对老年代进行内存回收

  5. Serial Old是运行在Client模式默认的老年代垃圾回收器

  6. Serial Old在Server模式下主要有两个用途:

    1. 与新生代的Parallel Scavenge配合使用
    2. 作为老年代CMS收集器的后备垃圾收集方案

在这里插入图片描述

Serial/Serial Old是一个单线程的回收器,其单线程的意义不仅说明它只会使用一个线程去完成回收工作,更重要的是它在进行垃圾回收时,必须暂停其它所有工作线程(STW),直到回收结束


Serial的优缺点对比:

  • 优点
    • 简单且高效(与其它单线程回收器相比),对于限定单个CPU的环境来说,Serial回收器由于没有线程交互的开销,专心做垃圾回收自然可以在单线程环境下达到最高的回收效率
    • 在内存不大(几十MB至一两百MB)的应用场景下,垃圾回收不频繁发生,使用串行回收器是可以接收的。
  • 缺点
    • 随着多核CPU的发展,串行的回收器已很少使用
    • 对于交互性强的应用,这种回收器是不能接受的,回收时会导致用户线程完全停止

4、ParNew 回收器:并行回收

//使用ParNew
-XX:+UseParNewGC
// 表示年轻代使用并行收集器,不影响老年代

Serial GC是年轻代中的单线程垃圾回收器,而ParNew回收器是Serial回收器的多线程版本,Par是Parallel的缩写,New表示只能处理年轻代,ParNew除了使用多线程回收垃圾外,几乎和Serial没有区别

ParNew回收器采用复制算法,STW机制,并行的方式对年轻代进行垃圾回收,ParNew是很多JVM运行在Server模式下年轻代的默认垃圾回收器

在这里插入图片描述

ParNew回收器适合运行在多CPU的环境下,由于可以充分利用多CPU,多核心等物理硬件优势,可以更快速完成垃圾回收,提升程序的吞吐量,但是在单个CPU环境下,ParNew回收器不如Serial更高效,因为单CPU使用Serial 回收器不需要频繁的切换线程

除了Serial外,只有ParNew GC能和CMS收集器配合工作

5、Parallel 回收器:吞吐量优先

//使用Parallel 
-XX:+UseAdaptiveSizePolicy

HotSpot的年轻代除了ParNew回收器是基于并行回收的以外,Parallel回收器也采用了复制算法,并行回收和STW机制对年轻代进行回收

Parallel的出现并不是多此一举,

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

高吞吐量可以高效率地利用CPU时间,尽快完成程序地运算任务,主要适合运行在后台不要太多交互的任务,如订单处理,工资支付,科学计算等应用程序,自适应调节策略也是Parallel与ParNew的一个重要区别

Parallel回收器在JDK1.6提供了用于老年代垃圾回收的Parallel Old回收器,用于替代Serial Old回收器,Parallel Old回收器采用标记-压缩算法,并行回收和STW机制对老年代进行垃圾回收

在这里插入图片描述

在程序吞吐量优先的应用场景中,Parallel和Parallel Old组合使用在Server模式下的回收性能很不错,Parallel和Parallel Old都是并行回收器均可以设置进行垃圾回收的线程数,但是回收线程数不是越多越好,太多的线程来回切换会影响垃圾回收的性能

JDK1.8中,默认使用Parallel 和Parallel Old组合

6、CMS回收器:低延迟

在学习之前,先了解三色标记清除算法:三色标记清除算法

-XX:+UseConcMarkSweepGC
// 启用CMS回收器,年轻代会自动使用ParNewGC

在JDK1.5时期,HotSpot推出一款在强交互应用中划时代意义的垃圾回收器,CMS(Concurrent-Mark-Sweep) 这款回收器是HotSpot中第一款真正意义上的并发收集器,第一次实现了用户线程和回收线程同时工作

CMS的关注点是尽可能缩短垃圾回收时用户线程的停顿时间,停顿时间越短(延迟越低)越适合和用户交互的程序,良好的响应速度能提升用户的使用体验,目前很大一部分Jaba程序集中在互联网或B/S服务器端上,这类应用尤其重视服务的响应速度,希望系统停顿的时间越短越好,以给用户带来更好的体验,CMS就符合此类应用的需求。

CMS采用标记-清除算法,并发执行和STW机制对老年代进行垃圾回收

CMS的工作过程主要分为4个步骤:

初始标记,并发标记,重新标记,并发清理

在这里插入图片描述

6.1 初始标记(Initial-Mark)

在初始标记的过程中,工作线程都将会因为STW出现短暂的暂停,这个阶段的主要任务仅仅只是标记出GC Roots能直接关联到的对象,一旦标记完成之后就会恢复之前被暂停的所有用户线程。

由于直接关联的对象比较小,所有这里的速度非常快。

6.2 并发标记(Concurrent-Mark)

从GC Roots的直接关联的对象开始遍历其它挂在引用链上的对象,这个过程耗时较长但是不需要暂停用户线程,可以与用户线程并发执行

6.3 重新标记(ReMark)

由于并发标记过程中,用户线程和GC标记线程并发执行,因此需要在清除前再重新标记一遍,保证对象的引用关系正确. 重新标记无法判断并发标记阶段用户线程产生的"浮动垃圾" 这个阶段也需要进STW,时间比初始标记稍长,但比并发标记要短的多

6.4 并发清除(Concurrent-Sweep)

此阶段清理已经死亡的对象,由于不需要移动存活对象。这个阶段也可以和用户线程并发执行,但标记-清除算法会产生内存碎片

6.5 CMS的特点

  1. 尽管CMS使用并发回收,但是初始标记重新标记这两个阶段仍然需要STW暂停用户线程,不过暂停的时间不会太长,所有的回收器都不可能不需要STW机制,只能尽量减少STW的时间,没有STW就无法保证清除时对象的引用关系不发生变化

  2. 由于最耗时的并发标记和并发清除都可以和用户线程并发执行,因此CMS整体的回收是低延迟

  3. 由于并发清除阶段和用户线程并发执行,边回收垃圾,边制造垃圾,所以在CMS并发清除阶段,需要确保用户线程有足够的内存可用,因此CMS不能像其它收集器一样等到老年代被填满后再进行回收,而是当老年代使用率达到某一阈值时立刻开始工作,如果CMS运行期间预留的内存无法满足用户线程的需求,就会出现一次"Concurrent Mode Failure"失败,临时启用Serial Old重新对老年代进行回收

  4. CMS使用标记-清除算法,意味着每次清除后就会产生内存碎片,因此使用CMS回收器的堆分配空间时只能使用空闲列表来为对象分配空间,虽然标记-压缩算法能避免内存碎片,但是却无法应用到CMS中,因为在并发清除阶段,一边清除死亡对象,一边执行用户线程,如果使用标记-压缩算法,那么堆中的对象会被移动,引用就会发生变化,正在执行的用户线程依据原先持有的对象地址可能找不到该对象

6.6 优缺点对比

优点:

  • 并发回收
  • 低延迟

缺点

  • 产生内存碎片

    如果老年代碎片化严重,此时一个大对象入堆,会提前出发full GC

  • CMS收集器对CPU资源敏感

    在并发阶段,虽然不会停止用户线程。但是会因为占用了一部分线程导致程序变慢,总吞吐量降低

  • CMS收集器无法处理浮动垃圾

    在并发标记阶段由于用户线程和GC标记线程并发执行,并发标记阶段如果产生新的垃圾,CMS将无法对这些垃圾进行标记。重新标记阶段只能对原先怀疑是垃圾的对象进行判断,最终导致这些新产生的垃圾只能在下一次被回收

CMS虽然能达到不错的低延迟,但是带来了相当多的弊端,清除阶段无法整理内存,导致老年代充斥大量内存碎片,且当老年代预留内存不足时,会启用Serial Old作为临时回收器,而Serila Old是串行独占的回收器,放在现在大部分的应用场景中,如果迎来一次业务高峰,那么用户可能会感受到数秒甚至更长的停顿

在JDK9中,CMS被标记为废弃,在JDK14中CMS被删除

7、基础回收器小结

如果想要最小化使用内存和并行开销,选择Serial GC

如果想要最大化应用程序的吞吐量,选择Parallel GC

如果想要最小化GC的中断或停顿时间,选择CMS GC

8、G1回收器:区域化分代式

8.1 G1概述

几个疑问?

  1. HotSpot已经内置很多回收器了,为什么还需要发布G1?

    原因在于随着应用程序越来越庞大,复杂,用户越来越多
    没有GC就不能保证程序正常运行,但GC经常造成的STW又跟不上实际的需求 所以才会不断对GC进行优化
    G1回收器是JDK7之后引入的新的垃圾回收器,是当今垃圾回收技术发展的前沿成果之一
    
    为了适应不断扩大的内存和不断增加的处理器数量,进一步降低延迟,同时兼顾良好的吞吐量
    官方给G1设定的目标是延迟可控的情况下尽可能获得更高的吞吐量
    G1担当起了全功能收集器的重任和期望
    
  2. 为什么名字叫做Garbage First(G1)?

    因为G1是一个并行回收器,它把堆内存分割成很多不相关的区域(Region) 这些区域物理上可以是不连续的
    使用不同的Region来表示Eden,S0,S1,Old等
    
    G1有计划地避免了整个Java堆中进行全区域地垃圾收集
    G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需要的时间)
    在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region
    
    由于这种方式侧重回收垃圾最多的区域,所以G1可以理解为 Garbage First 即垃圾优先
    

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

在JDK9以后,G1成为了HotSpot的默认垃圾收集器,兼顾年轻代和老年代的垃圾回收,被官方称为“全功能的垃圾收集器”

8.2 G1的优势

  • 并行与并发
    • 并行性:G1在回收期间,可以有多个GC线程同时工作,有效利用多核的工作能力,此时用户线程STW
    • 并发性:G1拥有与用户线程交替执行的能力,部分工作可以和用户线程同时执行。因此一般情况下,不会在整个回收阶段发生完全阻塞应用程序的情况
  • 分代分区收集
    • 从分代上看,G1仍属于分代型垃圾回收器,它会区分年轻代和老年代,年轻代依然有Eden和S,但从堆的角度看,它不要求年轻代或老年代是连续,也不再坚持固定大小和固定数量

      在这里插入图片描述

    • G1将堆空间分为若干个区域(Region),这些区域包含逻辑上的年轻代和老年代

      在这里插入图片描述

    • 同时兼顾年轻代和老年代

  • 空间整合

    G1将内存划分成一个个的Region,内存的回收是以Region作为基本单位的,Region的回收使用复制算法,但整体上可以看作是标记-压缩算法,这两种算法都可以避免内存碎片,这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC,尤其当Java堆非常大时,G1的优势更加明显

  • 可预测的停顿时间模型

    这是G1相对于CMS的另一大优势,G1除了追求低延迟外,还能建立可预测的停顿时间模型,这能让使用者明确指定在一长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒

    • 由于分区的原因,G1可以只选取部分Region进行回收,这样缩小了回收的范围,因此对发生停顿的情况也能得到较好的控制
    • G1跟踪各个Region里面的垃圾堆积的价值大小,在后台维护一个优先列表,**每次根据允许的收集时间,优先回收价值最大的Region,**保证了G1在有限的时间内可以
    • 相对于CMS,G1未必能做到最低的延迟,但是最差情况要好很多

8.3 G1的缺点

相较于CMS,G1还不具备全方位,压倒性优势,如用户程序运行过程中,G1无论是为了垃圾收集产生的内存占用(Footprint)还是程序运行时的额外执行负载(Overload)都要比CMS高

从经验上来说,在小内存应用上CMS的表现大概率会优于G1,而G1在大内存应用上会发挥其优势,平衡点在6-8G之间

8.4 G1的常见操作步骤

G1的设计原则就是简化JVM性能调优,只需简单三步即可完成调优:

  1. 开启G1垃圾回收器(JDK9之前)

    -XX:+UseG1GC
    
  2. 设置堆的最大内存

    -Xmx
    
  3. 设置GC最大停顿时间

    -XX:MaxGCPauseMillis
    //默认是200ms,JVM会尽力实现,但不保证一定达到
    

G1中提供了三种垃圾回收模式,YoungGC,Mixed GC,Full GC,在不同的条件下被触发

8.5 G1的适用场景

  1. G1主要面向服务器端应用,针对具有大内存,多处理器的机器(在普通大小的堆中表现并不出色)
  2. 最主要应用于需要降低延迟,并且堆很大的程序G1通过每次只清理一部分而不是全部的Region的增量式清理来保证每次GC停顿时间不会过长
  3. 替换CMS回收器,在下面情况下,使用G1会比使用CMS更好
    1. 超过50%的Java堆被活动数据占用
    2. 对象分配频率或年代提升频率变化很大
    3. GC停顿时间过长(长于0.5s到1s)
  4. 在HotSpot中的所有垃圾收集器,除了G1外,其它回收器均使用其内置的JVM线程执行GC操作,而G1可以使用用户线程承担后台运行的GC工作,即当JVM的GC线程处理速度变慢时,系统会调用用户线程来帮助加速垃圾回收过程

8.6 Region的介绍

使用G1收集器时,它将整个Java堆划分成2048个大小相同的独立的Region,每个Region的大小根据堆空间的实际大小而定,整体被控制在1MB到32MB之间且为2的N次幂,可以通过**-XX:G1HeapRegionSize**设定,所有的Region大小相同,且在JVM生命周期内不会被改变

虽然仍保留年轻代和老年代的概念,但年轻代和老年代不再是物理隔离的了,它们都是一部分Region(可以不连续)集合,通过Region的动态分配方式实现逻辑上的连续

在这里插入图片描述

一个Region可能属于Eden,S,或Old,但是一个Region活跃时只能属于一种区域,不能同时既作Eden又作Old,图中E表示该区域属于Eden,S表示属于Survivor,O表示属于Old,H表示Humongous主要存储大对象,如果一个对象的大小超过1.5个Region,就存放到H,空白表示尚未使用的内存空间,在Region中分配对象时,采用指针碰撞和TLAB

设置H的原因:

对于堆中的大对象,默认会被直接分配到老年代。但是如果它是一个短期存在的大对象,就会对垃圾回收器造成负面影响。为了解决这个问题,G1划分了Humongous区,用它专门存储大对象,如果一个H区装不下一个大对象,那么G1会寻找连续的H区来存储。为了找到连续的H区,有时不得不启动Full GC。G1的大多数行为都把H区作为老年代的一部分看待

8.7 G1回收器的回收环节

G1的垃圾回收主要包括三个环节:

  • 年轻代GC (Young GC)

  • 老年代并发标记过程(Concurrent Marking) 同时进行年轻代GC

  • 混合回收(Mixed GC) 进行年轻代GC和老年代GC

Young GC -> Young GC + Cocurrent Marking -> Mixed GC
如果需要,单线程,独占式,高强度的Full GC还是存在的, 它针对GC的评估失败提供了一种失败保护机制,即强力回收

在这里插入图片描述

G1的垃圾回收过程:

  1. 当年轻代Eden区内存用尽时,启动Yong Gc,G1的Young GC是一个并行的独占式回收器,暂停所有用户线程,启动多GC线程回收年轻代,然后将年轻代存活对象移动到S区或Old区

  2. 当堆内存使用到达一定值时(默认45%),开始老年代并发标记过程。并发标记过程进行时同时进行Young GC,GC线程将老年代存活对象标记并与用户线程并发执行

  3. 标记完成后马上开始混合回收

    G1将老年代中存活的对象移动到空闲空间,这些空闲空间就成了老年代的一部分。

    和年轻代不同,G1的老年代回收器不需要回收整个老年代,一次只需扫描/回收一小部分老年代的Region同时进行Young GC

9、G1垃圾回收过程详细说明

在这里插入图片描述

9.1 年轻代GC

JVM启动时,G1会先准备好Eden区,程序运行过程中不断创建对象到Eden中,当Eden空间耗尽后,G1会启动一次Young GC,Young GC只会回收Eden区和Survivor区

Young GC时,首先GC会STW停止用户线程的执行,G1创建回收集,回收集是指需要被回收的内存分段的集合,年轻代回收过程包括Eden区和Survivor区所有内存分段

在这里插入图片描述

Young GC的具体过程:

  1. 扫描根

    将GC Root和Rset作为GC Roots,作为可达性分析算法的根

  2. 更新Rset

    更新完Rset后,Rset可以准确反映出老年代对年轻代对象的引用

  3. 处理Rset

    识别被老年代对象指向的Eden中的对象,这些对象被认为存活

  4. 复制对象

    遍历GC Roots,Eden中存活对象被复制到Survivor区,Survivor区存活且年龄尚未超出阈值的对象年龄加1,年龄超出阈值的对象赋值到Old

  5. 处理引用

    处理Soft,Weak,Phantom,Final,JNI Weak等引用,最终清空Eden,GC停止工作

9.2 并发标记过程

  1. 初始标记

    标记从根节点直接可达的对象,此时STW,并触发一次Young GC

  2. 根区域扫描

    扫描Survivor区直接可达的老年代对象,并标记被引用的对象

  3. 并发标记

    在整个堆中进行并发标记与用户线程并发执行,若发现Region中所有对象都是垃圾,则该区域会被立刻回收,并发标记阶段会计算每个Region的对象活性(区域中存活对象的比例)

  4. 重新标记

    STW修正并发标记的结果,G1采用了比CMS更快的初始快照算法(SATB)

  5. 独占清理

    计算各个区域的存活对象和GC回收比例,并进行排序,是STW的,识别可以混合回收的区域,这个阶段不会真正回收

  6. 并发清理

    识别并清理完全空闲的空间

9.3 混合收集

当越来越多垃圾进入到老年代时,需要进行一次Mixed GC,避免内存被耗尽

Mixed GC会进行一次Young GC回收整个年轻代,并回收一部分老年代,而不是全部老年代,从而对GC时间进行控制

在这里插入图片描述

9.4 Full GC

GC的初衷就是避免Full GC的出现,如果经过上述三个环节后不能正常工作,G1会STW,并使用单线程的内存回收算法进行垃圾回收,性能会非常差,延迟很高

导致Full GC的原因可能有两个:

  1. ,没有足够的Region作为S区存放经过Young GC后Eden中存活的对象
  2. 并发标记过程完成前空间耗尽

增加堆内存大小,能有效回避Full GC

10、经典垃圾回收器总结

截至JDK1.8,一共有7款不同的垃圾回收器,每一款都有不同的特点,在具体使用时,需要根据具体的情况选择不同的垃圾回收器

在这里插入图片描述

如何选择垃圾回收器?

  1. 优先调整堆的大小让JVM自适应完成

  2. 如果堆内存小于100M,使用串行回收器

  3. 如果是单核,单机程序,且对延迟不敏感,使用串行回收器

  4. 如果是多核,需要高吞吐,选择并行回收器

  5. 如果是多核,追求低延迟,使用并发回收器

  6. 现在互联网项目基本都是使用G1,能控制延迟并且保持高吞吐

没有最好的万能的回收器,调优只针对特定场景,特定需求,不存在一劳永逸的回收器

11、新时期的垃圾回收器

11.1 垃圾回收器的新发展

GC仍然在不断地发展,G1也在不断地进行改进,例如原先G1的Full GC在JDK10之后已经是并行运行,在很多场景下,其表现还略优于Parallel GC的并行Full GC实现

即使是Serial GC,虽然比较古老,但是简单的设计和实现未必就是过时的,它本身的开销,不管是GC相关数据的开销,还是数据结构的开销,还是线程的开销,都是非常小的,随着云计算的兴起,在Serveless等新的应用场景下,Serial GC找到了新的舞台

比较不幸的是CMS,因为其算法存在的缺陷,虽然现在还有比较多的程序使用,但是在JDK9种已经被标记为废弃,并在JDK14中移除

11.2 Epsilon回收器和Shenandoah回收器

Epsilon回收器:又称为"无操作回收器",它只做内存的分配,不做垃圾的回收。适用于某些分配好空间,执行完就结束的短时间应用

Shenandoah回收器:称为"可伸缩的低延迟垃圾回收器"。主打低延迟的特点,宣称延迟时间与堆大小无关

11.3 革命性的ZGC

ZGC与Shenandoah目标高度相似,在尽可能对吞吐量影响不大的情况下,实现在任意堆大小下都可以把GC延迟时间控制在10ms之内

ZGC是一款基于Region内存布局,不设分代的,使用了读屏障,染色指针和内存多重映射等技术来实现可并发的标记-压缩算法的,以低延迟为首要目标的垃圾回收器

ZGC几乎在所有环节都是并发执行的,除了初始标记是STW的,所以停顿时间几乎都耗费在了初始标记上,这部分时间实际是非常少的

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值