彻底搞懂JVM的垃圾回收器

大家好,我是方木

前言

之前方木就带大家聊聊 JVM 垃圾回收机制(GC) 中的 彻底搞懂JVM的垃圾回收算法,今天继续带着大家来聊聊 JVM 垃圾回收机制(GC) 中的垃圾回收器,垃圾回收算法 可以简单的理解为 理论知识,垃圾回收器 则可以认为是 具体实现。由于Java虚拟机规范并没有对垃圾收集器如何实现进行明确的规定,所以不同的JVM厂商以及不同虚拟机版本所提供的垃圾收集器都可能会有较大的差别,并且一般也都会提供对应的参数,供用户根据自己的应用特点组合出各个内存区块,主要是新生代、老年代所使用的收集器。

接下来方木带着大家一起看看都有哪些垃圾收集器以及它们的特点。在详细介绍具体垃圾收集器之前,我们先看下这些收集器适用的范围以及组合配对关系

垃圾回收器适用的范围以及组合配对关系

1999 年随 JDK1.3.1 一起来的是串行方式Serial GC,它是第一款垃圾回收器;此后,JDK1.4 和 J2SE1.3 相继发布。2002 年 2 月 26 日,J2SE1.4 发布;Parallel GCConcurrent Mark Sweep (CMS) GC 跟随 JDK1.4.2 一起发布,并且 Parallel GC 在 JDK6 之后成为 HotSpot 默认 GC。这三个垃圾回收器也是各有千秋Serial GC 适合最小化地使用内存和并行开销的场景;Parallel GC 适合最大化应用程序吞吐量的场景;CMS GC 适合最小化中断或停顿时间的场景。

不过随着应用程序所应对的业务越来越庞大、复杂,用户越来越多,没有合适的回收器就不能保证应用程序正常进行,而经常造成 STW 停顿的回收器又跟不上实际的需求,所以才会不断地尝试对搜集器进行优化。Garbage First(G1)GC 正是面向这种业务需求所生,它是一个并行回收器,把堆内存分割为很多不相关的区间(Region);每个区间可以属于老年代或者年轻代,并且每个年龄代区间可以是物理上不连续的。

垃圾回收器概要

标记阶段完成后,GC 开始进入下一阶段,删除不可达对象,关于标记阶段有几个关键点是值得注意的:

  • 开始进行标记前,需要先暂停应用线程,否则如果对象图一直在变化的话是无法真正去遍历它的。暂停应用线程以便 JVM 可以尽情地收拾家务的这种情况又被称之为安全点(Safe Point),这会触发一次 Stop The World(STW) 暂停。触发安全点的原因有许多,但最常见的应该就是垃圾回收了。
  • 暂停时间的长短并不取决于堆内对象的多少也不是堆的大小,而是存活对象的多少。因此,调高堆的大小并不会影响到标记阶段的时间长短。

Serial GC

串行回收器主要有两个特点:第一,它仅仅使用单线程进行垃圾回收;第二,它独占式的垃圾回收。在串行回收器进行垃圾回收时,Java 应用程序中的线程都需要暂停,等待垃圾回收的完成,这样给用户体验造成较差效果。虽然如此,串行回收器却是一个成熟、经过长时间生产环境考验的极为高效的 回收器。新生代串行处理器使用复制算法,实现相对简单,逻辑处理特别高效,且没有线程切换的开销在诸如单 CPU 处理器或者较小的应用内存等硬件平台不是特别优越的场合,它的性能表现可以超过并行回收器和并发回收器。在 HotSpot 虚拟机中,使用 -XX:+UseSerialGC 参数可以指定使用新生代串行回收器和老年代串行回收器。当 JVMClient 模式下运行时,它是默认的垃圾回收器。老年代串行回收器使用的是标记-压缩算法。和新生代串行回收器一样,它也是一个串行的、独占式的垃圾回收器。由于老年代垃圾回收通常会使用比新生代垃圾回 收更长的时间,因此,在堆空间较大的应用程序中,一旦老年代串行回收器启动,应用程序很可能会因此停顿几秒甚至更长时间。虽然如此,老年代串行回收器可以 和多种新生代回收器配合使用,同时它也可以作为 CMS 回收器的备用回收器。若要启用老年代串行回收器,可以尝试使用以下参数:-XX:+UseSerialGC: 新生代、老年代都使用串行回收器。

Serial GC 工作步骤

ParNew GC

并行回收器是工作在新生代的垃圾回收器,它只简单地将串行回收器多线程化。它的回收策略、算法以及参数和串行回收器一样。
并行回收器 也是独占式的回收器,在收集过程中,应用程序会全部暂停。但由于并行回收器使用多线程进行垃圾回收,因此,在并发能力比较强的 CPU 上,它产生的停顿时间要短于串行回收器,而在单 CPU 或者并发能力较弱的系统中,并行回收器的效果不会比串行回收器好,由于多线程的压力,它的实际表现很可能比串行回收器差。开启并行回收器可以使用参数 -XX:+UseParNewGC,该参数设置新生代使用并行回收器,老年代使用串行回收器。老年代的并行回收回收器也是一种多线程并发的回收器。和新生代并行回收回收器一样,它也是一种关注吞吐量的回收器。老年代并行回收回收器使用标记-压缩算法,JDK1.6 之后开始启用。

Parallel GC

Parallel Scavenge 收集器的特点是它的关注点与其他收集器不同,CMS 等收集器的关注点尽可能地缩短垃圾收集时用户线程的停顿时间,而 Parallel Scavenge 收集器的目标则是达到一个可控制的吞吐量(Throughput)Parallel OldParallel Scavenge 收集器的老年代版本,使用多线程和“标记-整理”算法。这个收集器是在 JDK 1.6 中才开始提供的。使用 -XX:+UseParallelOldGC 可以在新生代和老生代都使用并行回收回收器,这是一对非常关注吞吐量的垃圾回收器组合,在对吞吐量敏感的系统中,可以考虑使用。参数 -XX:ParallelGCThreads 也可以用于设置垃圾回收时的线程数量。

Parallel GC 工作步骤

CMS GC

CMS( Concurrent Mark-Sweep )以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器,适用于对停顿比较敏感,并且有相对较多存活时间较长的对象(老年代较大)的应用程序;不过 CMS 虽然减少了回收的停顿时间,但是降低了堆空间的利用率。CMS GC 采用了 Mark-Sweep 算法,因此经过CMS收集的堆会产生空间碎片;为了解决堆空间浪费问题,CMS 回收器不再采用简单的指针指向一块可用堆空间来为下次对象分配使用。而是把一些未分配的空间汇总成一个列表,当 JVM 分配对象空间的时候,会搜索这个列表找到足够大的空间来存放住这个对象。另一方面,由于 CMS 线程和应用程序线程并发执行,CMS GC 需要更多的 CPU 资源。同时,因为 CMS 标记阶段应用程序的线程还是在执行的,那么就会有堆空间继续分配的情况,为了保证在 CMS 回收完堆之前还有空间分配给正在运行的应用程序,必须预留一部分空间。也就是说,CMS 不会在老年代满的时候才开始收集。相反,它会尝试更早的开始收集,已避免上面提到的情况:在回收完成之前,堆没有足够空间分配!默认当老年代使用 68% 的时候,CMS就开始行动了。 – XX:CMSInitiatingOccupancyFraction = n 来设置这个阀值。

CMS GC 工作步骤

  • 初始标记(STW initial mark):在这个阶段,需要虚拟机停顿正在执行的任务,官方的叫法 STW(Stop The Word)。这个过程从垃圾回收的"根对象"开始,只扫描到能够和"根对象"直接关联的对象,并作标记。所以这个过程虽然暂停了整个 JVM,但是很快就完成了。
  • 并发标记(Concurrent marking):这个阶段紧随初始标记阶段,在初始标记的基础上继续向下追溯标记。并发标记阶段,应用程序的线程和并发标记的线程并发执行,所以用户不会感受到停顿。
  • 并发预清理(Concurrent precleaning):并发预清理阶段仍然是并发的。在这个阶段,虚拟机查找在执行并发标记阶段新进入老年代的对象(可能会有一些对象从新生代晋升到老年代,或者有一些对象被分配到老年代)。通过重新扫描,减少下一个阶段"重新标记"的工作,因为下一个阶段会 Stop The World
  • 重新标记(STW remark):这个阶段会暂停虚拟机,回收器线程扫描在 CMS 堆中剩余的对象。扫描从"跟对象"开始向下追溯,并处理对象关联。
  • 并发清理(Concurrent sweeping):清理垃圾对象,这个阶段回收器线程和应用程序线程并发执行。
  • 并发重置(Concurrent reset):这个阶段,重置CMS回收器的数据结构,等待下一次垃圾回收。

G1 GC

G1 GCJDK 1.7 中正式投入使用的用于取代 CMS 的压缩回收器,它虽然没有在物理上隔断新生代与老生代,但是仍然属于分代垃圾回收器;G1 GC 仍然会区分年轻代与老年代,年轻代依然分有 Eden 区与 Survivor 区。G1 GC 首先将堆分为大小相等的 Region,避免全区域的垃圾收集,然后追踪每个 Region 垃圾堆积的价值大小,在后台维护一个优先列表,根据允许的收集时间优先回收价值最大的Region;同时 G1 GC 采用 Remembered Set 来存放 Region 之间的对象引用以及其他回收器中的新生代与老年代之间的对象引用,从而避免全堆扫描。

G1 GC 分区

随着 G1 GC 的出现,Java 垃圾回收器通过引入 Region 的概念,从传统的连续堆内存布局设计,逐步走向了物理上不连续但是逻辑上依旧连续的内存块;这样我们能够将某个 Region 动态地分配给 EdenSurvivor、老年代、大对象空间、空闲区间等任意一个。每个 Region 都有一个关联的 Remembered Set(简称 RS),RS 的数据结构是 Hash 表,里面的数据是 Card Table (堆中每 512byte 映射在 card table 1byte)。简单的说 RS 里面存在的是 Region 中存活对象的指针。当 Region 中数据发生变化时,首先反映到 Card Table 中的一个或多个 Card 上,RS 通过扫描内部的 Card Table 得知 Region 中内存使用情况和存活对象。在使用 Region 过程中,如果 Region 被填满了,分配内存的线程会重新选择一个新的 Region,空闲 Region 被组织到一个基于链表的数据结构(LinkedList)里面,这样可以快速找到新的 Region

总结而言,G1 GC 的特性如下:

  • 并行性:G1在回收期间,可以有多个GC线程同时工作,有效利用多核计算能力;
  • 并发性:G1拥有与应用程序交替执行的能力,部分工作可以和应用程序同时执行,因此,一般来说,不会在整个回收阶段发生完全阻塞应用程序的情况;
  • 分代GC:G1依然是一个分代回收器,但是和之前的各类回收器不同,它同时兼顾年轻代和老年代。对比其他回收器,或者工作在年轻代,或者工作在老年代;
  • 空间整理:G1在回收过程中,会进行适当的对象移动,不像CMS只是简单地标记清理对象。在若干次GC后,CMS必须进行一次碎片整理。而G1不同,它每次回收都会有效地复制对象,减少空间碎片,进而提升内部循环速度;
  • 可预见性:为了缩短停顿时间,G1建立可预存停顿的模型,这样在用户设置的停顿时间范围内,G1会选择适当的区域进行收集,确保停顿时间不超过用户指定时间。

G1 GC 工作步骤

  • 初始标记:标记一下GC Roots能直接关联的对象并修改TAMS值,需要STW但耗时很短。
  • 并发标记:从GC Root从堆中对象进行可达性分析找存活的对象,耗时较长但可以与用户线程并发执行。
  • 最终标记:为了修正并发标记期间产生变动的那一部分标记记录,这一期间的变化记录在Remembered Set Log 里,然后合并到Remembered Set里,该阶段需要STW但是可并行执行。
  • 筛选回收:对各个Region回收价值排序,根据用户期望的GC停顿时间制定回收计划来回收。

我的微信公众号:Java架构师进阶编程
专注分享Java技术干货,包括JVM、SpringBoot、SpringCloud、数据库、架构设计、面试题、电子书等,期待你的关注!在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

方木丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值