JVM内存和垃圾回收-15.垃圾回收器

1.GC分类和性能指标

1.1 垃圾回收器分类
  • 按线程数:这里的线程数指的用于垃圾回收的线程数,且两者都会STW
    • 串行回收器:单CPU上性能较好,默认被应用在客户端的JVM中
    • 并行回收器:在并发能力强的CPU上,停顿时间短

在这里插入图片描述

  • 按工作模式:

    • 并发式垃圾回收器:与用户线程交替执行,减少程序的停顿时间
    • 独占式垃圾回收器:一运行就停止程序中所有用户线程,直到垃圾回收完成
  • 按碎片处理方法:

    • 压缩式垃圾回收器:回收完后对存活对象进行压缩整理,消除回收后的碎片
    • 非压缩式垃圾回收器:回收完后对存活对象不进行压缩操作
  • 按工作的内存区间:

    • 年轻代垃圾回收器
    • 老年代垃圾回收器
1.2 评估指标

在最大吞吐量优先的情况下,降低暂停时间

  • 吞吐量:运行用户代码时间占总运行时间的比例(高吞吐量意味着程序运行越快,需要降低内存回收的执行频率,导致需要更长的暂停时间执行内存回收)
  • 垃圾收集开销:垃圾收集需要时间占总运行时间的比例
  • 暂停时间:垃圾回收时,工作线程被暂停的时间(低暂停时间意味着低延迟,需要频繁进行内存回收,导致垃圾收集的总时间变大,吞吐量下降)
  • 收集频率:相当于应用程序的执行,收集操作发生的频率
  • 内存占用:堆区占用内存大小(内存越大意味着一次执行垃圾收集的时间越长,即延迟越大)
  • 快速:一个对象从诞生到被回收经历的时间

在这里插入图片描述


2.不同垃圾回收器概述

2.1 7个经典垃圾收集器
  • 串行回收器:Serial(第一款GC)、Serial Old
  • 并行回收器:ParNew(Serial多线程版本)、Parallel Scavenge、Parallel Old(JDK8默认使用Parallel Scavenge+Parallel Old)
  • 并发回收器:CMS、G1(JDK9默认)

在这里插入图片描述

2.2 7个垃圾收集器和垃圾分代间的关系

在这里插入图片描述

2.3 垃圾收集器的组合关系

连线表示组合关系,虚拟表示废弃的组合或收集器

在这里插入图片描述


3.Serial回收器-串行回收

在这里插入图片描述

3.1 Serial
  • 客户端模式下默认的新生代垃圾收集器(因为它在单CPU下性能不错)

  • 采用复制算法

  • 串行回收且使用STW机制

3.2 Serial Old
  • 采用标记压缩算法

  • 串行回收且使用STW机制

  • 客户端模式下默认的老年代垃圾收集器(因为它在单CPU下性能不错)

  • 在服务端主要有两种用途:

    • 与新生代的Parallel Scavenge配合使用
    • 作为老年代CMS的后备垃圾收集方案
3.3 优缺点
  • 优点
    • 简单且高效
    • 没有线程交互的开销(该收集器限定在单CPU)
    • 运行在客户端不错(因为客户端内存不大,单线程可在较短时间内完成垃圾收集)
  • 缺点:
    • 该收集器在多核效率不高(现在机器都是多核)
    • 不适用交互性较强的应用

-XX:+UserSerialGC可以指定年轻代和老年代都使用Serial收集器


4.ParNew回收器-并行回收

在这里插入图片描述

  • Serial收集器的多线程版本

  • 只能处理新生代

  • 采用复制算法

  • 并行回收且使用STW机制

  • 很多JVM在服务端模式下默认的新生代垃圾收集器

对于新生代,由于回收次数频繁,所以使用并行方式高效;对于老年代,由于回收次数少,所以使用串行方式节省资源(多线程需要切换,消耗资源)。ParNew在新生代是并行回收,是不是就一定比Serial收集器高效?No!要分在单CPU环境还是多CPU环境

  • 在多CPU环境下,ParNew可以充分利用多CPU,快速完成收集,提升吞吐量
  • 在单CPU环境下,ParNew并不比Serial高效,因为Serial不需要切换线程,避免额外开销

-XX:+UserParNewGC可以指定年轻代使用ParNew

-XX:ParallelGCThreads限制线程数量


5.Parallel回收器-吞吐量优先

在这里插入图片描述

5.1 Parallel Scavenge

在年轻代中ParNew收集器已经基于并行回收了,为什么还要Parallel Scavenge收集器?

  • 与ParNew不同,Parallel Scavenge目标达到可控制的吞吐量
  • 与ParNew不同,Parallel Scavenge存在自适应调节策略(年轻代的大小、Eden区和Survivor的比例等参数会被自动调整,达到堆大小、吞吐量和停顿时间间的平衡点)
  • 采用复制算法
  • 并行回收且使用STW机制
  • JDK8默认的新生代收集器

高吞吐量可以高效利用CPU时间,尽快完成程序的运算任务,适合在后台计算且不需要太多交互的任务(如订单处理等)

5.2 Parallel Old
  • 采用标记压缩算法
  • 并行回收且使用STW机制
  • JDK8默认的老年代收集器

-XX:+UserParallelGC可以指定年轻代使用Parallel Scavenge;-XX:+UserParallelOldGC可以指定老年代使用Parallel Old(这两个参数一个开启,默认另一个也会被开启)

-XX:ParallelGCThreads限制线程数量


6.CMS回收器-低延迟

CMS:Concurrent-Mark-Sweep,JDK14已经被删除

在这里插入图片描述

6.1 四个阶段

CMS整个过程分为四个阶段:

  • 初始标记阶段:用户线程会STW,垃圾回收线程只是标记出GC Roots能直接关联的对象,标记完就恢复线程,因为直接关联对象少,所以该阶段执行速度快

  • 并发标记:从GC Roots的直接关联对象开始遍历所有对象,耗时较长但是不需要暂停用户线程

  • 重新标记:在并发标记阶段,由于用户线程没有暂停所以可能会出现对象的变动。该阶段修正那部分变动对象的标记记录(被修正的变动对象是值原来是垃圾但是变更为非垃圾的对象,并不是因为用户线程执行由非垃圾而变为垃圾的对象),相较于并发标记阶段时间短,但是用户线程需要STW

  • 并发清除:清理标记阶段判断已死亡的对象,释放内存空间。由于不需要移动存活对象,所以该阶段可以与用户线程并发执行(不需要STW)

6.1 优点
  • HotSpot中第一款真正意义的并发收集器(即第一次实现让垃圾收集线程和用户线程同时工作)
  • 尽可能缩短垃圾收集时间(在初始标记和重新标记阶段还是存在短暂的STW),即低延迟
  • 采用标记清除算法
  • 并发回收且使用STW机制
  • 在老年代中使用

低延迟适用于与用户交互的程序,比如B/S系统的服务端与网站

6.2 缺点
  • 会产生内存碎片(若干次GC后才进行一次碎片整理),导致会出现无法分配大对象的情况下触发Full GC
  • 总吞吐量降低(垃圾收集线程和用户线程并发执行相当于占用了部分用户线程,即对CPU资源敏感)
  • 无法处理浮动垃圾(并发标记阶段如果产生新的垃圾对象,CMS无法对这些垃圾对象进行标记,这些对象不会被及时回收,只能等下次GC)

由于在CMS垃圾收集中用户线程没有中断,所以要确保用户线程由足够内存可以使用,所以CMS不能等老年代完全被填满再收集,而是等堆内存使用率达到阈值后开始回收。

如果预留内存无法满足用户线程使用,则出现Concurrent Mode Failure,JVM会临时启动Serial Old重新进行老年代收集(用户线程停顿时间就变长了)

标记清除算法会产生内存碎片,为什么不使用标记压缩算法?

采用压缩算法整理内存时,对象地址会被修改,而用户线程还在运行,其使用的内存被压缩整理后无法使用

-XX:+UserConcMarkSweepGC可以指定老年代使用CMS,开启后会自动开启ParNew


7.G1回收器-区域化分代式

7.1 介绍
  • 目标是在延迟可控(即低的STW时间)的情况下获得尽可能高的吞吐量

  • 把堆划分为不相关的区域Region(物理上不连续),用于表示Eden区、survivor0、survivor1、老年代等

  • 进行全区域的垃圾回收

  • 每次根据允许的收集时间,优先回收价值较大的Region(Garbage First命名由来):

    • 它会跟踪每个Region中垃圾堆积的价值(即回收后获得空间大小和需要时间的经验值),并在后台维护优先列表
  • 针对多核CPU和大容量内存的机器(如服务端应用)

  • JDK9后默认垃圾回收器

-XX:+UseG1GC:指定G1收集器

-XX:G1HeapRegionSize:设置Region的大小

-XX:MaxGCPauseMillis:设置期望达到的最大GC停顿时间指标

7.2 优点
  • 并行和并发:
    • 并行性:G1可以有多个GC线程同时工作,当然用户线程还是会STW
    • 并发性:G1的部分工作可以和应用程序同时执行,即不会在整个回收阶段完全阻塞应用程序
  • 分代收集:
    • 依旧属于分代型垃圾回收器,只不过从堆结构上不要求Eden区、survivor区和老年代是连续的,也不固定大小和数量
    • 同时兼顾了年轻代和老年代
    • 将堆空间分为若干区域,这些区域包含了逻辑上的年轻代和老年代

在这里插入图片描述

  • 空间整合:
    • G1将内存划分为多个Region,回收时以Region为单位
    • Region之间采用复制算法,但是从整体上看做标记压缩算法(避免了内存碎片,堆越多G1的优势越明显)
  • 可预测的停顿时间模型:即能让使用者指定在M毫秒的时间片内,消耗垃圾收集上的时间不超过N毫秒
    • G1可只选取部分区域进行回收(控制了回收时间)
    • 每次根据允许收集的时间,优先回收价值较大的Region,保证了在有限时间内较高的收集效率
7.3 缺点
  • 相较于CMS,G1在GC时占用的内存和负载都要高
  • 在小内存应用上CMS表现大概率优于G1
7.4 什么是Region?
  • 所有Region大小一样,为2的幂次方
  • 一个Region可能属于Eden(E)、Survivor(S)或Old(O)区域中的某个角色(当然被回收后可以成为其他角色),同时加入了Humongous(H)区,用于存储大对象(对象超过了1.5个Region就放到H)

为什么要设置H?对于堆中的大对象默认直接放到老年代,但如果它只是短期存在的大对象,而老年代回收频率低,相当于内存泄漏(该对象要被收集却因为清除频率低未被收集),所以使用H区装大对象(如果一个H区装不下就找连续的H区,找不到连续的H区就Full GC)

7.5 垃圾回收过程
  • 年轻代GC:使用并行(多个垃圾回收线程)独占式(出现STW)收集器
  • 老年代并发标记:堆内存使用达到一定值(默认45%)开始老年代并发标记过程
  • 混合回收:包括年轻代GC和老年代GC
7.5.1 什么是Remembered Set?

一个Region对象可能会被其他Region引用,意味着回收年轻代时还需要扫描老年代(即对整个堆进行扫描,相当于Full GC了),降低了GC效率,如何避免这种扫描?

  • 每个Region都有对应的一个Remembered Set

  • 对每个引用类型数据进行写操作时(即引用其他对象),会产生一个写屏障暂停中断,然后检查要引用的对象是否和引用类型数据在同一个Region中:

    • 不在同一个则通过CardTable把引用信息记录到引用类型对象所在的Region的Remembered Set(记录这个Region清理时需要扫描哪几个Region,判断其他Region是否还引用它)
  • 垃圾收集时,把Remembered Set加入到GC Roots(回收年轻代时,老年代的对象相当于GC Roots)的枚举范围中

在这里插入图片描述

7.5.2 年轻代GC
  • 首先G1停止应用程序,并且创建回收集(需要被回收的内存分段集合,年轻代GC时该集合包括所有Eden区和Survivor区)

  • 然后开始以下过程:

    • 扫描GC Roots和Remembered Set引用的对象(避免全堆扫描)
    • 更新Remembered Set,保证Remembered Set
    • 处理Remembered Set
    • 复制对象(即把Eden区存活的对象复制到Survivor区空的内存段)
    • 处理引用
7.5.3 并发标记过程
  • 初始标记阶段
  • 根区域扫描
  • 并发标记
  • 再次标记
  • 独占清理
  • 并发处理阶段
7.5.4 混合回收

回收全部年轻代和部分老年代


总结

不同垃圾收集器的区别

在这里插入图片描述

如何选择垃圾回收器?
  • 优先调整堆的大小,让JVM自适应完成
  • 如果内存小于100M,使用串行收集器
  • 如果是单核/单机程序且没有停顿时间要求,使用串行收集器
  • 如果是多CPU、需要高吞吐量且允许停顿时间超过1S,使用并行收集器
  • 如果是多CPU、追求低停顿时间(即低延迟),使用并发收集器

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值