热门标签 | 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 虚拟机》第三版


推荐阅读
  • 深入解析JVM垃圾收集器
    本文基于《深入理解Java虚拟机:JVM高级特性与最佳实践》第二版,详细探讨了JVM中不同类型的垃圾收集器及其工作原理。通过介绍各种垃圾收集器的特性和应用场景,帮助读者更好地理解和优化JVM内存管理。 ... [详细]
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • 深入解析 Apache Shiro 安全框架架构
    本文详细介绍了 Apache Shiro,一个强大且灵活的开源安全框架。Shiro 专注于简化身份验证、授权、会话管理和加密等复杂的安全操作,使开发者能够更轻松地保护应用程序。其核心目标是提供易于使用和理解的API,同时确保高度的安全性和灵活性。 ... [详细]
  • 深入解析Redis内存对象模型
    本文详细介绍了Redis内存对象模型的关键知识点,包括内存统计、内存分配、数据存储细节及优化策略。通过实际案例和专业分析,帮助读者全面理解Redis内存管理机制。 ... [详细]
  • 1:有如下一段程序:packagea.b.c;publicclassTest{privatestaticinti0;publicintgetNext(){return ... [详细]
  • 网络攻防实战:从HTTP到HTTPS的演变
    本文通过一系列日记记录了从发现漏洞到逐步加强安全措施的过程,探讨了如何应对网络攻击并最终实现全面的安全防护。 ... [详细]
  • 2023年京东Android面试真题解析与经验分享
    本文由一位拥有6年Android开发经验的工程师撰写,详细解析了京东面试中常见的技术问题。涵盖引用传递、Handler机制、ListView优化、多线程控制及ANR处理等核心知识点。 ... [详细]
  • 非公版RTX 3080显卡的革新与亮点
    本文深入探讨了图形显卡的进化历程,重点介绍了非公版RTX 3080显卡的技术特点和创新设计。 ... [详细]
  • 本文详细探讨了Java中的24种设计模式及其应用,并介绍了七大面向对象设计原则。通过创建型、结构型和行为型模式的分类,帮助开发者更好地理解和应用这些模式,提升代码质量和可维护性。 ... [详细]
  • 本文介绍了Java并发库中的阻塞队列(BlockingQueue)及其典型应用场景。通过具体实例,展示了如何利用LinkedBlockingQueue实现线程间高效、安全的数据传递,并结合线程池和原子类优化性能。 ... [详细]
  • 本文详细介绍了 Dockerfile 的编写方法及其在网络配置中的应用,涵盖基础指令、镜像构建与发布流程,并深入探讨了 Docker 的默认网络、容器互联及自定义网络的实现。 ... [详细]
  • 本文深入探讨了Linux系统中网卡绑定(bonding)的七种工作模式。网卡绑定技术通过将多个物理网卡组合成一个逻辑网卡,实现网络冗余、带宽聚合和负载均衡,在生产环境中广泛应用。文章详细介绍了每种模式的特点、适用场景及配置方法。 ... [详细]
  • 使用Python在SAE上开发新浪微博应用的初步探索
    最近重新审视了新浪云平台(SAE)提供的服务,发现其已支持Python开发。本文将详细介绍如何利用Django框架构建一个简单的新浪微博应用,并分享开发过程中的关键步骤。 ... [详细]
  • 网易严选Java开发面试:MySQL索引深度解析
    本文详细记录了网易严选Java开发岗位的面试经验,特别针对MySQL索引相关的技术问题进行了深入探讨。通过本文,读者可以了解面试官常问的索引问题及其背后的原理。 ... [详细]
  • 本文深入探讨了 Redis 的两种持久化方式——RDB 快照和 AOF 日志。详细介绍了它们的工作原理、配置方法以及各自的优缺点,帮助读者根据具体需求选择合适的持久化方案。 ... [详细]
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社区 版权所有