jvm学习笔记2:垃圾收集器

写在前面

本文只要GC的几个关键性能指标开始,详细介绍目前主流的几个垃圾收集器的原理。
参考:深入理解JVM(5) : Java垃圾收集器G1垃圾收集器介绍

1.两个GC关键指标

1.1 吞吐量

吞吐量=程序运行时间/(程序运行时间+垃圾回收时间),越高越好。

1.2 暂停时间

一个时间段内应用程序线程让与GC线程执行而完全暂停,越小越好。

1.3 两者关系

”高吞吐量”和”低暂停时间”是一对相互竞争的目标。应用线程在GC期间必须停止(或者仅在GC的特定阶段,这取决于所使用的算法),然而这会增加额外的线程调度开销(直接开销是上下文切换,间接开销是因为缓存的影响)。 加上JVM内部安全措施的开销,这意味着GC及随之而来的不可忽略的开销,将增加GC线程执行实际工作的时间。 也就是说每次GC都有固定的开销,这将将=增加GC线程执行实际工作的时间。因此我们希望尽量少的进行GC,以减少这些固定开销的时间,从而提高吞吐量。
然后,当我们减少GC次数,提高吞吐量后。意味每次GC的对象变多,单个GC需要花更多时间来完成, 从而导致更高的平均和最大暂停时间。因此,考虑到低暂停时间,最好频繁地运行GC以便更快速地完成,这又导致了吞吐量的下降。
综上所述,我们可以看到高吞吐量和低暂停时间是两个对立且矛盾的目标。
在这里插入图片描述

1.4 GC调优目标

  • 高吞吐量
  • 低延迟时间(也就是低暂停时间)
    接下来我们介绍的垃圾收集器基本都是从这两个指标出发,进行了优化。

2. 垃圾收集器分类

从不同角度来看垃圾收集器,将其分为几个方面。

  • 按垃圾回收线程数目:串行Serial、并行Parallel
    串行垃圾收集器: 同一时间段,只能有一个垃圾收集线程工作,用户线程停止
    并行垃圾收集器:同一时间段,可以有多个垃圾收集线程工作(单CPU,交替执行;多CPU,同时执行)
  • 按工作模式分:并发式、独占式
    并发式: 垃圾收集线程和用户线程交替工作,以尽可能的减少程序的停顿时间
    独占式: 垃圾收集线程工作时,用户线程停止
  • 按碎片处理方式分:压缩式和 非压缩式
    压缩式: 在回收完成后,对存活对象进行压缩整理,消除回收后的空间碎片
    非压缩式:不做内存空间整理
  • 按工作的内存区间分:年轻代和老年代

3. 七种经典垃圾收集器+ZGC

3.1 经典垃圾收集器的发展

  1. 1999年随JDK1.3.1一 起来的是串行方式的Serial GC,它是第一款GC。ParNew垃圾收集器是Serial收集器的多线程版本,老年代为Serial Old GC;
  2. 2002年2月26日,Parallel GC和Concurrent Mark Sweep GC跟随JDK1.4.2一起发布;
  3. Parallel GC 在 JDK6 之后成为 Hotspot 默认GC;
  4. 2012年,在 JDK1.7u4 中,G1 可用;
  5. 2017年,JDK9 中 G1 成为默认垃圾收集器,以替代 CMS;
  6. 2018年3月,JDK10 中 G1 的并行完整垃圾回收,实现并行性能改善最坏情况的延迟;
  7. 2018年9月,JDK11 发布。引入 Epsilon GC ,又称为“No-Op(无操作)” 回收器;同时引入 ZGC: 可伸缩的低延迟回收器(Experimental);
  8. 2019年3月,JDK12 发布。增强 G1,自动返回未使用堆内存给操作系统; 同时,引入Shenandoah GC:低停顿时间的GC(Experimental);
  9. 2019年9月,JDK13 发布。增强 ZGC,自动返回未使用堆内存给操作系统;
  10. 2020年3月,JDK14 发布。删除 CMS。扩展 ZGC 在 mac 和 windows 的应用。

3.2 各个垃圾收集器之间的搭配关系

  • 两个收集器之间有连线,表示可以搭配使用;
  • CMS GC和Serial Old GC之间的连线,标识Serial Old作为CMS出现“Concurrent Mode Failure”失败后的后备方案;
  • 红色虚线表示由于维护和兼容性测试的成本,在JDK 8中声明为废弃,在JDK 9中移除;
  • 绿色虚线表示在JDK 14中:弃用这个组合;
  • 青色虚线表示在JDK 14中,删除了CMS垃圾收集器

在这里插入图片描述

3.3 Serial GC

在这里插入图片描述
1. 简介
Serial收集器是最基本、发展历史最悠久的收集器,曾经(在JDK 1.3.1之前)是虚拟机新生代收集的唯一选择。虚拟机运行在Client模式下的默认新生代收集器。
特性:新生代、单线程、STW、复制算法

2. 特性
简单而高效(与其他收集器的单线程比),对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。

3.4 ParNew GC

1. 简介
ParNew收集器其实就是Serial收集器的多线程版本,除了使用多条线程进行垃圾收集之外,其余行为包括Serial收集器可用的所有控制参数、收集算法、Stop The World、对象分配规则、回收策略等都与Serial收集器完全一样,在实现上,这两种收集器也共用了相当多的代码。
2.特性
新生代、多线程、STW、复制算法
3. 应用场景
ParNew收集器是许多运行在Server模式下的虚拟机中首选的新生代收集器。
4.其它
注:在并行的垃圾收集器中,只有它才能与CMS搭配使用,所以如果想在老年代使用CMS,年轻带最好使用ParNew GC,因为Serial GC是单线程的。

在这里插入图片描述

3.5 Parallel Scavenge

在这里插入图片描述

1.简介
Parallel Scavenge收集器是一个新生代收集器,它也是使用复制算法的收集器,又是并行的多线程收集器。
2. 特性
吞吐量优先、自适应调节策略
3. 应用场景
适应吞吐量大,对停顿时间没有太大要求的后台系统。
4.关键参数

  • 吞吐量大小 -XX:GCTimeRatio
  • 最大垃圾收集停顿时间 -XX:MaxGCPauseMillis
  • 自适应调节 -XX:+UseAdaptiveSizePolicy

3.6 Serial Old GC

1. 特性
Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用标记-整理算法。
2.应用场景
Client模式
Serial Old收集器的主要意义也是在于给Client模式下的虚拟机使用。
Server模式:
如果在Server模式下,那么它主要还有两大用途:一种用途是在JDK 1.5以及之前的版本中与Parallel Scavenge收集器搭配使用,另一种用途就是作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用。

3.7 Parallel Old GC

1.特性
Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。
2.应用场景
在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge加Parallel Old收集器。
这个收集器是在JDK 1.6中才开始提供的,在此之前,新生代的Parallel Scavenge收集器一直处于比较尴尬的状态。原因是,如果新生代选择了Parallel Scavenge收集器,老年代除了Serial Old收集器外别无选择(Parallel Scavenge收集器无法与CMS收集器配合工作)。由于老年代Serial Old收集器在服务端应用性能上的“拖累”,使用了Parallel Scavenge收集器也未必能在整体应用上获得吞吐量最大化的效果,由于单线程的老年代收集中无法充分利用服务器多CPU的处理能力,在老年代很大而且硬件比较高级的环境中,这种组合的吞吐量甚至还不一定有ParNew加CMS的组合“给力”。直到Parallel Old收集器出现后,“吞吐量优先”收集器终于有了比较名副其实的应用组合。

3.8 CMS GC(Concurrent Mark Sweep GC)

在这里插入图片描述
1.特性
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。
CMS收集器是基于“标记—清除”算法实现的,它的运作过程相对于前面几种收集器来说更复杂一些,整个过程分为4个步骤:

  • 初始标记(CMS initial mark)
    初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,需要“Stop The World”。

  • 并发标记(CMS concurrent mark)
    并发标记阶段就是进行GC Roots Tracing的过程。(这个阶段对象的引用发生宝变化或者新new的对象怎么办?Remembered Set+Write Barrier
    来解决)

  • 重新标记(CMS remark)
    重新标记阶段是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短,仍然需要“Stop The World”。(比如说并发标记期间新建的对象,这时候好像有个记忆集Rset的概念,将新的对象及引用发生变化的对象都加入Rset,然后在这个阶段进行mark)

  • 并发清除(CMS concurrent sweep)
    并发清除阶段会清除对象。

2. 缺点

  • 对CPU资源敏感。因为在并发阶段,需要多个线程执行垃圾回收线程

  • CMS无法处理浮动垃圾。并发标记已经标记为可用的对象由于用户线程继续执行变成了垃圾,并发清理阶段产生的垃圾。这两种都是浮动垃圾,需要等到下一次垃圾回收
    -由于并发标记和并发清除时,用户线程还在执行,这个过程中可能会产生大量的对象,导致堆中预留的空间不够,这时候会出现Concurrent Mode Failure。虚拟机将启动后备预案:临时启用Serial Old GC,停顿时间加长。

  • 由于清除阶段是采用的标记-清除算法,会产生空间碎片问题。这是因为这个阶段垃圾线程和用户线程是并发的,对象的位置不能发生变化,所以不能采用标记-整理算法。那么我们怎么解决空间碎片化的问题呢?-XX:+UseCMSCompactFullCollection开关参数,用于CMS收集器顶不住要进行FullGC时开始内存碎片的合并整理过程。-XX:CMSFullGCsBeforeCompaction,用于设置执行多少次不压缩的Full GC后,跟着来一次带压缩的。

3.9 G1 GC

1. 特性
G1是一款面向服务端应用的垃圾收集器。

  1. 并行与并发
    G1能充分利用多CPU、多核环境的硬件优势,缩短STW时间,部门其余垃圾收集器需要STW的动作,G1能够并发。
  2. 分代收集
    G1能够独立回收整个堆,年轻代和老年代
  3. 空间整合
    与CMS的标记-清除不同。从整体上看,G1是标记-整理算法;从局部来看,是复制算法。这两种算法都不会产生空间碎片
  4. 可预测的停顿
    能根据回收region的数量大小,来实现可预测的停顿。
  5. 内存划分
    堆内存被划分为多个大小相等的内存块(Region),每个Region是逻辑连续的一段内存,结构如下:
    在这里插入图片描述
    每个Region被标记了E、S、O和H,说明每个Region在运行时都充当了一种角色,其中H是以往算法中没有的,它代表Humongous,这表示这些Region存储的是巨型对象(humongous object,H-obj),当新建对象大小超过Region大小一半时,直接在新的一个或多个连续Region中分配,并标记为H。
    堆内存中一个Region的大小可以通过-XX:G1HeapRegionSize参数指定,大小区间只能是1M、2M、4M、8M、16M和32M。默认分为2048份。

2. GC模式
回收过程

2.1 young gc

发生在年轻代的GC算法,一般对象(除了巨型对象)都是在eden region中分配内存,当所有eden region被耗尽无法申请内存时,就会触发一次young gc,这种触发机制和之前的young gc差不多,执行完一次young gc,活跃对象会被拷贝到survivor region或者晋升到old region中,空闲的region会被放入空闲列表中,等待下次被使用。

2.2 mixed gc

当越来越多的对象晋升到老年代old region时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即mixed gc,该算法并不是一个old gc,除了回收整个young region,还会回收一部分的old region,这里需要注意:是一部分老年代,而不是全部老年代,可以选择哪些old region进行收集,从而可以对垃圾回收的耗时时间进行控制。

  • 那么mixed gc什么时候被触发?
    先回顾一下cms的触发机制,如果添加了以下参数:
    -XX:CMSInitiatingOccupancyFraction=80
    -XX:+UseCMSInitiatingOccupancyOnly
    当老年代的使用率达到80%时,就会触发一次cms gc。相对的,mixed gc中也有一个阈值参数 -XX:InitiatingHeapOccupancyPercent,当老年代大小占整个堆大小百分比达到该阈值时,会触发一次mixed gc.=mixed gc的执行过程有点类似cms,主要分为以下几个步骤:
  1. initial mark: 初始标记过程,整个过程STW,标记了从GC Root可达的对象
  2. concurrent marking: 并发标记过程,整个过程gc collector线程与应用线程可以并行执行,标记出GC Root可达对象衍生出去的存活对象,并收集各个Region的存活对象信息
  3. remark: 最终标记过程,整个过程STW,标记出那些在并发标记过程中遗漏的,或者内部引用发生变化的对象
  4. clean up: 垃圾清除过程,如果发现一个Region中没有存活对象,则把该Region加入到空闲列表中

2.3 full gc

如果对象内存分配速度过快,mixed gc来不及回收,导致老年代被填满,就会触发一次full gc,G1的full gc算法就是单线程执行的serial old gc,会导致异常长时间的暂停时间,需要进行不断的调优,尽可能的避免full gc。

关于详细过程,可参考《可能是最全面的 Java G1学习笔记》《深入理解 Java G1 垃圾收集器GC调优[转]》

3.10 最牛逼的ZGC

看了马士兵大佬的视频和几个博客,来补充一波,需要的请去:马士兵大佬的ZGC讲解ZGC垃圾收集器
 ZGC全称是Z Garbage Collector,是一款可伸缩(scalable)的低延迟(low latency garbage)、并发(concurrent)垃圾回收器,旨在实现以下几个目标:

  • 停顿时间不超过10ms
  • 停顿时间不随heap大小或存活对象大小增大而增大
  • 可以处理从几百兆到几T的内存大小(最大4T)

关键技术:

  1. 指针标记(Pointer tagging Or Colored Pointers )
    ZGC利用指针的64位中的几位表示Finalizable、Remapped、Marked1、Marked0,以标记该指向内存的存储状态。相当于在对象的引用上标注了对象的信息(不是对象头)。在这个被指向的内存发生变化的时候(内存在Compact整理被移动时),颜色就会发生变化。
    在这里插入图片描述
  2. 读屏障(read barrier)
    在每次读取指针时,会根据指针的颜色做相应的操作。

4. 几个关键算法

4.1 三色标记法(CMS和G1有用到)

1. 定义

要找出存活对象,根据可达性分析,从GC Roots开始进行遍历访问,可达的则为存活对象。将遍历对象图过程中遇到的对象,按“是否访问过”这个条件标记成以下三种颜色:
白色:尚未访问过
黑色:本对象已访问过,而且本对象象引用到 的其他对象也全部访问过了
灰色:本对象已访问过,但是本对引用到的其他对象尚未全部访问完。全部访问后,会转换为黑色
2. 并发标记中的两个问题之浮动垃圾
一个已经标记为黑色或者灰色的引用断开,变为垃圾。但由于已经被标记了,无法当做垃圾回收,只能等到下一次垃圾回收,称之为浮动垃圾。如下图,B、C称之为浮动垃圾。
在这里插入图片描述

3.并发标记中的两个问题之漏标
一个黑色对象重新引用一个新的对象,产生漏标;
一个灰色对象断开了白色对象的引用,黑色对象重新引用了该对象,产生漏标。
比如下图,C就会漏标
在这里插入图片描述4. Incremental Update+writer barrier (CMS:增量更新+写屏障)
一个黑色对象引用额一个新的对象,将这个新的对象记录下来,在重新标记阶段再来遍历标记
5.SATB+writer barrier(G1:Snapshot At The Beginning)
(以下是自己的理解,请各位指正)
1.在开始的标记的时候,生成一个快照,认为这些对象都是存活的。
2.如果A->B的引用断开,把 B记录下来。(writer barrier)
3.通过RSet记录记录每个对象上,引用它的集合。比如B->C,则C的RSet里面有B
4.在重新标记阶段,重新扫描所有引用变化的对象。如果RSet为空,则这个对象是垃圾。

4.2 RSet( Remember set)、CSet(collection set)

RSet:记录一个Region里的对象被哪些其他Region引用。
CSet:录了GC要收集的Region集合。GC时只需扫描CSet中各个Rset即可。
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值