关于后端:你需要知道的垃圾收集器和算法

6次阅读

共计 3817 个字符,预计需要花费 10 分钟才能阅读完成。

前言

上文讲了 JVM 内存模型及理论知识,而本文将介绍垃圾收集相干算法及几种垃圾收集器。垃圾回收器也叫垃圾收集器,不同的厂商对垃圾收集器的实现也是不同的,这里次要介绍目前应用最宽泛的 OracleJDK 中自带的 HotSpot 虚拟机中的几个垃圾收集器。

你用过哪些垃圾回收器?它们有什么区别?

《Java 虚拟机标准》并没有对垃圾收集器的具体实现做任何的规定,因而每家垃圾收集器的实现形式都不同,但比拟罕用的垃圾回收器是 OracleJDK 中自带的 HotSpot 虚拟机。HotSpot 中应用的垃圾收集器次要包含 7 个:Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS 和 G1(Garbage First)收集器。

其中 Serial 收集器属于最晚期的垃圾收集器,也是 JDK 1.3 版本之前惟一的垃圾收集器。它是单线程运行的垃圾收集器,其单线程是指在进行垃圾回收时所有的工作线程必须暂停,直到垃圾回收完结为止,如下图所示:

Serial 收集器的特点是简略和高效,并且自身的运行对内存要求不高,因而它在客户端模式下应用的比拟多。

ParNew 收集器实际上是 Serial 收集器的多线程并行版本,运行示意图如下图所示:

Parallel Scavenge 收集器和 ParNew 收集器相似,它也是一个并行运行的垃圾回收器;不同的是,该收集器关注的侧重点是实现一个能够管制的吞吐量。而这个吞吐量计算的也很奇怪,它的计算公式是:用户运行代码的工夫 /(用户运行代码的工夫 + 垃圾回收执行的工夫)。比方用户运行的工夫是 8 分钟,垃圾回收运行的工夫是 2 分钟,那么吞吐量就是 80%。Parallel Scavenge 收集器谋求的指标就是将这个吞吐量的值,管制在肯定的范畴内。

Parallel Scavenge 收集器有两个重要的参数:

  • -XX:MaxGCPauseMillis 参数:它是用来管制垃圾回收的最大进展工夫;
  • -XX:GCTimeRatio 参数:它是用来间接设置吞吐量的值的。

Serial Old 收集器为 Serial 收集器的老年代版本,而 Parallel Old 收集器是 Parallel Scavenge 收集器的老年代版本。

CMS(Concurrent Mark Sweep)收集器与以吞吐量为指标的 Parallel Scavenge 收集器不同,它强调的是提供最短的进展工夫,因而可能会就义肯定的吞吐量。它次要利用在 Java Web 我的项目中,它满足了零碎须要短时间进展的要求,以此来进步用户的交互体验。

Garbage First(简称 G1)收集器是历史倒退的产物,也是一款更先进的垃圾收集器,次要面向服务端利用的垃圾收集器。它将内存划分为多个 Region 分区,回收时则以分区为单位进行回收,这样它就能够用绝对较少的工夫优先回收蕴含垃圾最多区块。从 JDK 9 之后也成了官网默认的垃圾收集器,官网也举荐应用 G1 来代替抉择 CMS 收集器。

JVM 垃圾回收算法常见面试题

JVM 内存布局和垃圾回收算法是面试中常考的题目,也是咱们了解并优化 Java 程序的实践根底,而对于垃圾收集器来说除了目前支流版本(JDK 8)罕用的 CMS 之外,其余的垃圾收集器都属于面试中的加分项。对于 G1 和 JDK 11 中的 ZGC 的了解代表了你对技术的酷爱和新技术的敏感水平,也属于面试中的重要加分项。

和此知识点相干的面试题还有以下这些:

  1. 讲一下分代收集实践?
  2. CMS 收集器的具体执行流程是什么?
  3. CMS 存在哪些问题?
  4. CMS 收集器和 G1 的区别?
  5. 讲一下 JDK 11 中的 ZGC 收集器?

1. 分代收集

说到垃圾收集器不得不提的一个实践就是“分代收集”,因为目前商用虚拟机的垃圾收集器都是基于分代收集的实践进行设计的,它是指将不同“年龄”的数据调配到不同的内存区域中进行存储,所谓的“年龄”指的是经验过垃圾收集的次数。这样咱们就能够把那些朝生暮死的对象集中调配到一起,把不容易沦亡的对象调配到一起,对于不容易死亡的对象咱们就能够设置较短的垃圾收集频率,这样就能耗费更少的资源来实现更现实的性能了。

通常状况下分代收集算法会分为两个区域:新生代(Young Generation)和老年代(OldGeneration),其中新生代用于存储刚刚创立的对象,这个区域内的对象存活率不高,而对于通过了肯定次数的 GC 之后还存活下来的对象,就能够胜利升级到老生代了。

对于下面介绍的 7 个垃圾收集器来说,新生代垃圾收集器有:Serial、ParNew、Parallel Scavenge,老生代的垃圾收集器有:Serial Old、Parallel Old、CMS,而 G1 属于混合型的垃圾收集器,如下图所示:

2. CMS 收集器的具体执行流程

CMS 收集器是基于标记 - 革除算法实现的,咱们之前有讲过对于标记 - 革除的算法,这里简略地回顾一下。标记 - 革除的算法是由标记阶段和革除阶段形成的,标记阶段会给所有的存活对象做上标记;而革除阶段会把被标记为死亡的对象进行回收,而死亡对象的判断是通过援用计数法或者是目前支流的可达性剖析算法实现的。然而 CMS 的实现略微简单一些,它的整个过程能够分为四个阶段:

  • 初始标记(CMS initial mark)
  • 并发标记(CMS concurrent mark)
  • 从新标记(CMS remark)
  • 并发革除(CMS concurrent sweep)

首先,初始标记阶段的执行工夫很短,它只是标记一下 GC Roots 的关联对象;并发阶段是从 GC Roots 关联的对象进行遍历判断并标识死亡对象,这个过程比较慢,但不须要进行用户线程,用户的线程能够和垃圾收集线程并发执行;而从新标记阶段则是为了判断并标记,刚刚并发阶段用户持续运行的那一部分对象,所以此阶段的执行工夫也比拟短;最初是并发革除阶段,也就是革除下面标记的死亡对象,因为 CMS 应用的是标记 - 革除算法,而非标记 - 整顿算法,因而毋庸挪动存活的对象,这个阶段垃圾收集线程也能够和用户线程并发执行。

CMS 的整个执行过程中只有执行工夫很短的初始标记和从新标记须要 Stop The World(全局进展)的,执行过程如下图所示:

因为 CMS 是一款基于标记革除算法实现的垃圾收集器,因而会在收集时产生大量的空间碎片,为了解决这个问题,CMS 收集器提供了一个 -XX:+UseCMS-CompactAtFullCollection 的参数(默认是开启的,此参数从 JDK9 开始废除),用于在 CMS 收集器进行 Full GC 时开启内存碎片的合并和整顿。

但又因为碎片整顿的过程必须挪动存活的对象,所以它和用户线程是无奈并发执行的,为了解决这个问题 CMS 收集器又提供了另外一个参数 -XX:CMSFullGCsBefore-Compaction,用于规定多少次(依据此参数的值决定)之后再进行一次碎片整顿。

3. CMS 存在哪些问题?

CMS 的问题:

  • 并发标记并发革除,与用户线程抢占 CPU,可能会造成用户线程的效率低,默认回收线程(cpu+3)/4,当然当初的 CPU 个别都是双核,配置很高不须要思考
  • 在并发革除阶段长生的垃圾无奈回收,得期待下一次回收,产生浮动垃圾
  • 需预留内存空间给用户线程应用,参数 CMSInitiatingOccupancyFraction 设置回收阈值(老年代空间应用比例),默认 92%。失败时产生谬误:Concurremnt Mode Fail,启用备选计划 Serial Old 进行老年代回收
  • 标记革除产生碎片的问题,无奈找到足够间断内存空间调配以后对象时,通过以下参数来调节

    • -XX:UseCMSCompactAtFullCollection(默认开启),内存空间不够时进行内存整理。
    • -XX:CMSFullGCsBeforeCompaction:设置在执行多少次 Full GC 后对内存空间进行压缩整顿。默认值 0。

4. CMS 收集器和 G1 的区别?

CMS 垃圾回收器的机制:

CMS 以获取最小暂停工夫为目标,实质就是通过空间换工夫,实用于一些对响应工夫有很高要求的利用或网站

CMS 和 G1 区别:

  • G1 将内存划分为区域 Region,用户可指定垃圾回收限时,最大暂停工夫
  • G1 回收失去的内存空间是间断的,CMS 垃圾回收产生内存碎片

5. ZGC

ZGC 收集器是 JDK 11 中新增的垃圾收集器,它是由 Oracle 官网开发的,并且反对 TB 级别的堆内存治理,而且 ZGC 收集器也十分高效,能够做到 10ms 以内实现垃圾收集。

在 ZGC 收集器中没有新生代和老生代的概念,它只有一代。ZGC 收集器采纳的着色指针技术,利用指针中多余的信息位来实现着色标记,并且 ZGC 应用了读屏障来解决 GC 线程和利用线程可能存在的并发(批改对象状态的)问题,从而防止了 Stop The World(全局进展),因而使得 GC 的性能大幅晋升。

ZGC 的执行流程和 CMS 比拟类似,首先是进行 GC Roots 标记,而后再通过指针进行并发着色标记,之后便是对标记为死亡的对象进行回收(被标记为橘色的对象),最初是重定位,将 GC 之后存活的对象进行挪动,以解决内存碎片的问题。

总结

本文介绍了 JDK 11 之前的 7 种垃圾收集器:Serial、Serial Old、ParNew、Parallel Scavenge、Parallel Old、CMS、G1,其中 CMS 收集器是 JDK 8 之前的支流收集器,而 JDK 9 之后的默认收集器为 G1,并且在文章的最初,介绍了性能更加强悍、综合体现更好的 ZGC 收集器。

本文由 mdnice 多平台公布

正文完
 0