热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

jdk8默认垃圾回收器_JVM系列之经典垃圾回收器(上篇)

封面图1.前言随着JDK的不断更新,垃圾回收器的效率也越来越高。每一次JDK的更新,必然会包含有垃圾回收器的更新,截止目前,
0ebdc8579cd7c40888f31969c8a19d34.png
封面图

1.前言

随着 JDK 的不断更新,垃圾回收器的效率也越来越高。每一次 JDK 的更新,必然会包含有垃圾回收器的更新,截止目前,在最新的 JDK14 版本中,最新的垃圾回收器为 ZGC。

从垃圾回收器发展至今,出现过很多垃圾回收器,例如:Serial、ParNew、Parallel Scavenge、SerialOld、CMS、Parallel Old、G1、Shenandoah、ZGC 等,虽然目前比较流行的是 G1 和 ZGC,但是那些经典的垃圾回收器我们也有必要了解一下它们的工作原理,一方面是因为目前仍然有很多系统使用的都是 JDK8 及以下版本,而这些版本中有很多系统都是默认使用的经典的垃圾回收器,搞懂它们的原理,方便我们对它进行调优,另一方面就是为了面试,毕竟垃圾回收器是面试高频考点。

接下来本文将先介绍 Serial、ParNew、Parallel Scavenge、SerialOld、CMS、Parallel Old 这六款经典的垃圾回收器的工作原理,以及使用场景和相关参数配置,在下一篇文章中将会主要介绍 G1、Shenandoah、ZGC 这些 GC 的工作原理。

2.性能指标

在介绍垃圾回收器之前,先介绍一下衡量垃圾回收器的几个常用指标。

首先是「吞吐量」,它描述的是用户线程执行的时间比上全部运行时间(全部运行时间 = 用户线程时间+垃圾回收线程执行时间执行),吞吐量越高,表明系统的资源利用率越高。

然后是「停顿时间」,它表示的是 GC 线程在执行过程中,导致用户线程停顿的时间,如果停顿时间越长,那么用户线程卡顿的时间越长,用户体验越差,因此我们希望的是停顿时间越短越好。

另外还有一个指标就是「内存占用率」,因为垃圾回收器在执行过程中,它也需要占用一定的内存空间,当然我们期望的是内存占用率越小越好,尤其是在服务器内存配置较低的情况下。如果服务器的资源配置很高,内存很大,内存占用率高一点也可以接受。

通常情况下,「吞吐量和低延时(停顿时间)这两个指标是对立的」,无法同时兼顾两者,如果想追求低延时,那么吞吐量就会下降;如果追求高吞吐量,那么停顿时间就会变长。不过随着目前垃圾回收器的不断发展,越来越多的垃圾回收器都是以「在保证高吞吐量的情况下,尽可能的去追求低延时」为原则,来进行垃圾回收器的实现。

3.Serial

Serial 是针对新生代的垃圾回收器,它是单线程执行的,是一款串行的垃圾回收器,采用的是复制算法。它的单线程并不仅仅指它在进行垃圾回收时是单线程或者单处理器执行,更深的含义是它在垃圾回收时,需要暂停其他所有的线程,造成 STW。

当 JVM 处于客户端模式下时,Serial 是默认的垃圾回收器,它的优点是简单高效。在内存资源受限的环境下,Serial 垃圾回收器相比其他垃圾回收器,它所占用的内存更小。对于单处理器的场景,Serial 处理器由于是单线程的,它省去了线程之间的资源竞争,因此会更加高效。

当使用参数 「-XX:+UseSerialGC」时,在开启使用Serial垃圾回收器同时,老年代的垃圾回收器为Serial Old。

4.Serial Old

和 Serial 一样,Serial Old 也是单线程执行的,是一款串行的垃圾回收器,不同的是 Serail Old 回收的是老年代区域,采用的算法是标记-压缩(整理)算法。在进行垃圾回收时,同样也会造成 STW 的现象。

当 JVM 处于「客户端」模式下时,Serial Old 通常与 Serial 垃圾收集器搭配使用,Serial 回收新生代区域,Serial Old 回收老年代区域。Serial 和 Serial Old 搭配使用时的示意图如下。

48b3400ac79990d33af92617b310c57b.png
Serial/Serial Old 搭配进行垃圾回收示意图

当 JVM 处于「服务端」模式下时,Serial Old 有两个用途,其一:与 Parallel Scavenge 垃圾回收器(回收新生代)搭配使用;其二:作为 CMS 垃圾回收器的后备方案(这一点会在下面讲解 CMS 垃圾回收器时具体讲解)。其实 Serial Old 还可以与 ParNew 垃圾回收器搭配使用,不过这种组合方式,从 JDK9 开始,已经被移除了。

5.ParNew

ParNew 是一款针对新生代区域的垃圾回收器,它是 Serial 垃圾收集器的多线程版本,即它是一款并行的垃圾回收器,支持多个垃圾回收线程同时并行回收垃圾,使用的也是复制算法。ParNew 的大部分参数配置和 Serial 收集器一样,但额外多了部分参数,如:可以通过参数 「-XX:ParallelGCThreads」 来指定并行的垃圾回收的线程个数,默认情况下,垃圾回收线程的个数与处理器的个数相等。在单处理器的系统中,ParNew 的性能并不一定比 Serial 好,因为线程的切换需要额外耗费 CPU 资源。

可以使用参数 「-XX:+UseParNewGC」 来开启使用 ParNew 进行垃圾回收。

ParNew 可以和 Serial Old 或者 CMS 搭配使用,然而从 JDK9 开始,官方已经移除了 ParNew 和 Serial Old 的组合使用方式,同时 JDK9 中将 CMS 标记为 Deprecated 状态,在 JDK14 中彻底移除 CMS,这就导致了 ParNew 将处于一个十分尴尬的地位,在高版本中既不能和 Serial Old 搭配使用,也将在未来无法和 CMS 搭配使用,这就导致了 ParNew 这款垃圾回收器必然消失在历史的舞台。

aa2dcb9ebc997ac282c2c109a1550040.png
ParNew/Serial Old 搭配进行垃圾回收示意图

6.Parallel Scavenge

Parallel Scavenge 也是一款针对「新生代的并行的」垃圾回收器,它和 ParNew 虽然都是并行、针对新生代,但是它们的区别很大,Parallel Scavenge 是一款「吞吐量优先」的垃圾回收器。适用于那些期望尽可能的利用 CPU 资源、尽快完成程序的运算任务以及不太注重用户交互行为的场景。

Parallel Scavenge 提供了两个参数来精准地控制吞吐量,分别是 「MaxGCPauseMillis」「GCTimeRatio」

MaxGCPauseMillis 表示的是每次进行 GC 时,系统的最大停顿时间,如果配置了该参数,那么 JVM 在每次进行垃圾回收时,它会尽可能的将停顿时间控制在 MaxGCPauseMillis 之内。该参数并不是配置的越小越好,如果配置得很小,那么 JVM 可能会为了达到停顿时间控制在 MaxGCPauseMillis 之内的目的,选择以减小新生代区域的大小为代价,毕竟每次回收 300M 的空间所花的时间肯定比 500M 的短。「而 JVM 将新生代的内存区域调小后,带来的后果就是垃圾回收进行得更加频繁了,最后会导致系统的吞吐量下降」。通常情况下,我们无法精准地把控每次垃圾回收需要停顿的时间,所以该参数需要慎用,一不小心,配置的不合理,可能适得其反。

GCTimeRatio 表示的是每次 GC 的时间占用的比率是多少(具体计算方是:GCTimeRatio = 用户线程运行时间/ GC 线程运行时间),例如:如果 GCTimeRatio 参数的值配置的 19,那么 GC 运行的时间占总时间的 5%(1/(1+19))。JVM 通过这个参数来达到控制系统吞吐量的目的。

另外 JVM 还提供了一个参数,叫做「UseAdpativeSizePolicy」,它表示的是让 JVM「根据系统的运行情况来动态调整」新生代(Eden、S0、S1)、老年代的大小,我们只需要设置好最基本的内存参数以及 MaxGCPauseMillis(最大停顿时间)或者 GCTimeRatio(目标吞吐量)即可,不需要设置-XX:Xmn(新生代的内存大小)、-XX:SurvivorRatio (Surivivior区域的比例)等参数了,JVM 会根据系统运行时监控到相关信息,来动态进行调整。Parallel Scavenge 支持动态调整策略,这也算是它和 ParNew 收集器的另一大不同之处了。

7.Parallel Old

Parallel Old 是 Parallel Scavenge 收集器的老年代版本,也是支持多线程的并行执行,它底层是基于标记-压缩(整理)算法来实现的。在 JDK6 中才开始停供,在 Parallel Old 出现之前,Parallel Scavenge 收集器只能配合着 Serial Old 使用,无法与 CMS 垃圾回收器配合使用,这是因为 Parallel Scavenge 与 CMS、Serial、ParNew 这些收集器的底层框架不一样,无法兼容导致的。而 Serial Old 又是单线程的垃圾收集器,在多处理器的场景下,性能不高,白白浪费了 Parallel Scavenge 并行的优点,好车配劣马,所以在 Parallel Old 出现之前,Parallel Scavenge 一直处于比较鸡肋的地位。目前,Parallel Scavenge 和 Parallel Old 的组合,其垃圾回收效果不错,是 JDK8 中默认的垃圾回收组合方式。

3e87eaf3b7963897de5ee73468a906ac.png
Parallel Scavenge/Parallel Old 垃圾回收示意图

8.CMS

CMS 的全称是 Concurrent-Mark-Sweep 的缩写,翻译过来就是并发标记清除,它是一款「以低停顿时间为目标」的垃圾回收器,特点是低延时。CMS 的工作原理大致分为四个步骤:初始标记、并发标记、重新标记、并发清除。使用参数:「-XX:+UseConcMarkSweepGC」 即可开启使用 CMS 垃圾回收器。

  1. 「初始标记」指的是仅仅只标记出和 GC Roots 直接关联的对象,这个过程需要暂停所有的用户线程,因此会产生 STW。由于这一步仅仅标记和 GC Roots 直接关联的对象,因此这一步耗费的时间会很短,造成的停顿时间会很短。
  2. 「并发标记」。这一步是从和 GC Roots 直接关联的对象出发,开始遍历整个对象图引用链,这个过程是 GC 线程和用户线程并发执行的,因此不会造成 STW。这一步因为需要遍历所有对象的引用链,所以耗费时间较长,由于不会造成 STW,即使耗时较长,也没有关系。
  3. 「重新标记」。在并发标记阶段,用户线程仍然在运行,因此会改变对象之间的引用关系,那么在重新标记阶段,就是对并发标记的结果进行修正。把那些怀疑是垃圾,而实际不是垃圾的对象重新标记为存活对象。这一步需要暂停所有的用户线程,因此会造成 STW 的现象,这一步的耗时会比初始标记阶段长一些,但是远小于并发标记阶段的耗时。
  4. 「并发清除」。这一阶段是垃圾回收线程和用户线程一起并发执行,垃圾回收线程进行垃圾对象的清除,这一步耗时较长,但不会造成 STW。

整体上来看,CMS 垃圾回收器只有在初始标记阶段和重新标记阶段会造成用户线程的停顿,但是这两步都耗时较短,因此整体上,CMS 进行垃圾回收时,是低延时的。示意图如下。

e9c1815039ee3b12ff0ec8e4667524ac.png
CMS 垃圾回收示意图

8.1 CMS 优缺点与参数调优

CMS 垃圾回收器的优点就是低延时,对于那些期望快速响应、暂停时间较短以提高用户体验的网站或者系统,CMS 垃圾回收器就特别适合它们。

CMS 的缺点也很明显。第一,「对 CPU 资源比较敏感」。因为垃圾回收线程需要和用户线程并发执行,会涉及到争夺 CPU 资源的现象,导致系统的吞吐量下降。我们可以通过参数 「-XX:ParallelGCThreads」 来控制垃圾回收线程的数量,系统默认的数值是 「(处理器核心数+3)/4」

第二,CMS 「会产生"浮动"垃圾」。在 CMS 进行垃圾回收时,用户线程也在执行,在此过程中,用户线程可能也会产生新的垃圾,而这些新的垃圾在 CMS 进行本次垃圾回收时,是不会被回收掉的,这些垃圾被称之为"浮动"垃圾。也正是因为垃圾线程和用户线程存在同时执行的场景,因此 CMS 垃圾回收器,不会等到堆内存被使用完才进行垃圾回收,它需要为系统预留一部分内存以供用户线程运行,所以 CMS 通常是当堆内存使用率达到达到某一个阈值之后,就会触发执行垃圾回收。

该阈值在 JDK5 中,默认值为 68%,到了 JDK6 以后,该阈值的默认值被提高到了 92%,我们可以通过参数 「-XX:CMSInitiatingOccupancyFraction」 手动设置。在实际使用过程中,如果系统的内存使用增长比较慢,那么这个阈值就可以适当的设置得大一点,降低垃圾回收的频率,以提高系统性能。但如果设置得过大,也会带来额外的问题,当设置得过大时,也即是内存即将被使用完,此时由于用户线程仍在运行,如果恰好此时需要为一个比较大的对象分配内存空间,而剩余的内存不足了,此时就会出现“Concurrent Mode Failure”的日志提示,表示并发回收垃圾失败,那么 JVM 此时就不得不启动备选方案,冻结用户线程,采用 Serial Old 垃圾回收器进行老年代的垃圾回收,在一定程度上,可能降低系统的性能。

第三,CMS 「会产生垃圾碎片」。CMS 使用的是标记-清除算法,所以会产生内存碎片,内存碎片可能会影响程序性能。如果要为大对象分配内存时,发现内存中没有一块能容得下该对象的内存,那这个时候 JVM 就不得不触发一次 Full GC,导致程序发生 STW。为了减少内存碎片,CMS 的研发人员提供了一个开关,当开启这个开关时,会让垃圾回收器在 Full GC 完成后,进行一次内存碎片的整理,这个参数就是:「-XX:+UseCMSCompactAtFullCollection」, 「+」 号表示开启开关。

这个开关虽然解决了内存碎片带来的问题,但是,如果每次在 Full GC 之后都进行一次内存碎片的整理,而每次内存碎片的整理又需要暂停用户线程,造成 STW,这会使 CMS 具有低延时的特点大打折扣,因此 CMS 的研发人员还提供了另外一个参数:「-XX:CMSFullGCsBeforeCompaction」,该参数表示的含义是当发生多少次 Full GC 后,才对内存进行一次整理。

8.2 CMS 发展现状及未来

CMS 是一款针对老年代的垃圾回收器,它需要与新生代的垃圾回收器搭配使用,而且只能与 Serial、ParNew 这两款新生代的垃圾回收器搭配。在 G1 垃圾回收器出现之前,CMS 曾经因为是唯一一款并发的、低延时的垃圾回收器,其应用十分广泛,但是后来被同样具有并发清理、低延时,但性能却比 CMS 高效很多倍的 G1 垃圾回收器打破了局面,以至于在 JDK9 中,CMS 被标记为「Deprecated」,开始逐渐的淡出人们的视野,在目前最新的 JDK14 中,CMS 则是完全被移除了,成为了第一款被彻底遗弃的垃圾回收器。

9.总结

本文详细介绍了 Serial、ParNew、Parallel Scavenge、SerialOld、CMS、Parallel Old 这六款垃圾回收器,最后用一张图来总结它们相互之间搭配使用的关系。需要说明的是,在 JDK9 中,取消了 ParNew 与 Serial Old、Serial 与 CMS 的搭配组合,并且 CMS 被标记 Deprecated,在 JDK14 中被彻底移除。

9a146a6e70d3a6cbd9b428cd2a032669.png
垃圾回收器搭配组合

10.参考

  • 周志明《深入理解 Java 虚拟机》第三版


推荐阅读
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • 本文作者分享了在阿里巴巴获得实习offer的经历,包括五轮面试的详细内容和经验总结。其中四轮为技术面试,一轮为HR面试,涵盖了大量的Java技术和项目实践经验。 ... [详细]
  • 深入解析Java虚拟机(JVM)架构与原理
    本文旨在为读者提供对Java虚拟机(JVM)的全面理解,涵盖其主要组成部分、工作原理及其在不同平台上的实现。通过详细探讨JVM的结构和内部机制,帮助开发者更好地掌握Java编程的核心技术。 ... [详细]
  • 深入理解Java多线程并发处理:基础与实践
    本文探讨了Java中的多线程并发处理机制,从基本概念到实际应用,帮助读者全面理解并掌握多线程编程技巧。通过实例解析和理论阐述,确保初学者也能轻松入门。 ... [详细]
  • 深入剖析JVM垃圾回收机制
    本文详细探讨了Java虚拟机(JVM)中的垃圾回收机制,包括其意义、对象判定方法、引用类型、常见垃圾收集算法以及各种垃圾收集器的特点和工作原理。通过理解这些内容,开发人员可以更好地优化内存管理和程序性能。 ... [详细]
  • 本文详细探讨了Java中的24种设计模式及其应用,并介绍了七大面向对象设计原则。通过创建型、结构型和行为型模式的分类,帮助开发者更好地理解和应用这些模式,提升代码质量和可维护性。 ... [详细]
  • 本文介绍了Java并发库中的阻塞队列(BlockingQueue)及其典型应用场景。通过具体实例,展示了如何利用LinkedBlockingQueue实现线程间高效、安全的数据传递,并结合线程池和原子类优化性能。 ... [详细]
  • 网络攻防实战:从HTTP到HTTPS的演变
    本文通过一系列日记记录了从发现漏洞到逐步加强安全措施的过程,探讨了如何应对网络攻击并最终实现全面的安全防护。 ... [详细]
  • 本文深入探讨了Linux系统中网卡绑定(bonding)的七种工作模式。网卡绑定技术通过将多个物理网卡组合成一个逻辑网卡,实现网络冗余、带宽聚合和负载均衡,在生产环境中广泛应用。文章详细介绍了每种模式的特点、适用场景及配置方法。 ... [详细]
  • 本文详细探讨了Java中的ClassLoader类加载器的工作原理,包括其如何将class文件加载至JVM中,以及JVM启动时的动态加载策略。文章还介绍了JVM内置的三种类加载器及其工作方式,并解释了类加载器的继承关系和双亲委托机制。 ... [详细]
  • Docker的安全基准
    nsitionalENhttp:www.w3.orgTRxhtml1DTDxhtml1-transitional.dtd ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 深入解析:手把手教你构建决策树算法
    本文详细介绍了机器学习中广泛应用的决策树算法,通过天气数据集的实例演示了ID3和CART算法的手动推导过程。文章长度约2000字,建议阅读时间5分钟。 ... [详细]
  • 在金融和会计领域,准确无误地填写票据和结算凭证至关重要。这些文件不仅是支付结算和现金收付的重要依据,还直接关系到交易的安全性和准确性。本文介绍了一种使用C语言实现小写金额转换为大写金额的方法,确保数据的标准化和规范化。 ... [详细]
  • 本文探讨了如何在给定整数N的情况下,找到两个不同的整数a和b,使得它们的和最大,并且满足特定的数学条件。 ... [详细]
author-avatar
看山不是山
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有