JVM系列(六) - JVM垃圾回收器

前言

在之前的几篇博客中,我们大致介绍了,常见的 垃圾回收算法 及 JVM 中常见的分类回收算法。这些都是从算法和规范上分析 Java 中的垃圾回收,属于方法论。在 JVM 中,垃圾回收的具体实现是由 垃圾回收器Garbage Collector)负责。

正文

概述

在了解 垃圾回收器 之前,首先得了解一下垃圾回收器的几个名词。

1. 吞吐量

CPU 用于运行用户代码的时间与 CPU 总消耗时间的比值。比如说虚拟机总运行了 100 分钟,用户代码 时间 99 分钟,垃圾回收 时间 1 分钟,那么吞吐量就是 99%

吞吐量 = 运行用户代码时间/(运行用户代码时间 + 垃圾回收时间)

2. 停顿时间

停顿时间 指垃圾回收器正在运行时,应用程序 的 暂停时间。对于 独占回收器 而言,停顿时间可能会比较长。使用 并发回收器 时,由于垃圾回收器和应用程序 交替运行,程序的 停顿时间 会变短,但是,由于其 效率 很可能不如独占垃圾回收器,故系统的 吞吐量 可能会较低。

3. GC的名词

3.1. 新生代GC(Minor GC)

指发生在 新生代 的垃圾回收动作,因为 Java 对象大多都具备 朝生夕死 的特性,所以 Minor GC 通常 非常频繁,一般回收速度也比较快。

3.2. 老年代GC(Major GC)

指发生在 老年代 的垃圾回收动作,出现了 Major GC,经常会伴随至少一次的 Minor GC(发生这种情况,那么 整个堆 都 GC 一遍,通常称为 Full GC)。Major GC 的速度一般会比 Minor GC 慢 10 倍以上。

4. 并发与并行

4.1. 串行(Parallel)

单线程 进行垃圾回收工作,但此时 用户线程 仍然处于 等待状态

4.2. 并发(Concurrent)

这里的并发指 用户线程 与 垃圾回收线程 交替执行。

4.3. 并行(Parallel)

这里的并行指 用户线程 和多条 垃圾回收线程 分别在不同 CPU 上同时工作。

垃圾回收算法

1. 根搜索算法

根搜索算法 是从 离散数学 中的图论引入的,程序把所有引用关系看作一张图,从一个节点 GC ROOT 开始,寻找对应的 引用节点,找到这个节点后,继续寻找 这个节点 的 引用节点。当所有的引用节点寻找完毕后,剩余的节点 则被认为是 没有被引用到 的节点,即 无用 的节点。

上图 红色 为无用的节点,可以被 回收。目前 Java 中可以作为 GC ROOT 的对象有:

  1. 虚拟机栈 中引用的对象(本地变量表);
  2. 方法区 中 静态变量 引用的对象;
  3. 方法区 中 常量 引用的对象;
  4. 本地方法栈 中引用的对象(Native 对象)。

基本所有 GC 算法都引用 根搜索算法 这种概念。

2. 标记 - 清除算法

标记-清除算法 从 根集合 进行扫描,对 存活的对象 进行 标记。标记完毕后,再扫描整个空间中 未被标记 的对象进行 直接回收,如下图所示:

标记-清除算法 不需要进行 对象的移动,并且仅对 不存活 的对象进行处理,在 存活 的对象 比较多 的情况下 极为高效。但由于 标记-清除算法 直接回收不存活的对象,并没有对还存活的对象进行 整理,因此会导致 内存碎片

3. 复制算法

复制算法 将内存划分为 两个区间,使用此算法时,所有 动态分配 的对象都只能分配在 其中一个 区间(活动区间),而 另外一个 区间(空间区间)则是 空闲 的。

复制算法 同样从 根集合 扫描,将 存活 的对象 复制 到 空闲区间。当扫描完毕活动区间后,会的将 活动区间 一次性全部 回收。此时原本的 空闲区间 变成了 活动区间。下次 GC 时候又会重复刚才的操作,以此循环。

复制算法 在存活对象 比较少 的时候,极为高效,但是带来的成本是 牺牲一半的内存空间 用于进行 对象的移动。所以 复制算法 的使用场景,必须是对象的 存活率非常低 才行。最重要的是,我们需要克服 50% 的 内存浪费

4. 标记 - 整理算法

标记-整理算法 采用 标记-清除算法 一样的方式进行对象的 标记,但在回收 不存活的对象 占用的空间后,会将所有 存活的对象 往 左端空闲空间 移动,并更新对应的指针。

标记-整理 是在 标记-清除 之上,又进行了 对象的移动排序整理,因此 成本更高,但却解决了 内存碎片 的问题。

JVM 为了 优化内存 的回收,使用了 分代回收 的方式。对于 新生代内存 的回收(Minor GC)主要采用 复制算法。而对于 老年代内存 的回收(Major GC),大多采用 标记-整理算法

垃圾回收器

1. 垃圾回收器分类标准

2. 七种垃圾回收器概述

在 JVM 中,具体实现有 SerialParNewParallel ScavengeCMSSerial Old(MSC)Parallel OldG1 等。在下图中,你可以看到 不同垃圾回收器 适合于 不同的内存区域,如果两个垃圾回收器之间 存在连线,那么表示两者可以 配合使用

如果当 垃圾回收器 进行垃圾清理时,必须 暂停 其他所有的 工作线程,直到它完全收集结束。我们称这种需要暂停工作线程才能进行清理的策略为 Stop-the-World。以上回收器中, SerialParNewParallel ScavengeSerial OldParallel Old 均采用的是 Stop-the-World 的策略。

图中有 7 种不同的 垃圾回收器,它们分别用于不同分代的垃圾回收。

  • 新生代回收器:Serial、ParNew、Parallel Scavenge
  • 老年代回收器:Serial Old、Parallel Old、CMS
  • 整堆回收器:G1

两个 垃圾回收器 之间有连线表示它们可以 搭配使用,可选的搭配方案如下:

新生代 老年代
Serial Serial Old
Serial CMS
ParNew Serial Old
ParNew CMS
Parallel Scavenge Serial Old
Parallel Scavenge Parallel Old
G1 G1

3. 单线程垃圾回收器

3.1. Serial(-XX:+UseSerialGC)

Serial 回收器是最基本的 新生代 垃圾回收器,是 单线程 的垃圾回收器。由于垃圾清理时,Serial 回收器 不存在 线程间的切换,因此,特别是在单 CPU 的环境下,它的 垃圾清除效率 比较高。对于 Client 运行模式的程序,选择 Serial 回收器是一个不错的选择。

Serial 新生代回收器 采用的是 复制算法

3.2. Serial Old(-XX:+UseSerialGC)

Serial Old 回收器是 Serial 回收器的 老生代版本,属于 单线程回收器,它使用 标记-整理 算法。对于 Server 模式下的虚拟机,在 JDK1.5 及其以前,它常与 Parallel Scavenge 回收器配合使用,达到较好的 吞吐量,另外它也是 CMS 回收器在 Concurrent Mode Failure 时的 后备方案

Serial 回收器和 Serial Old 回收器的执行效果如下:

Serial Old 老年代回收器 采用的是 标记 - 整理算法

4. 多线程垃圾回收器(吞吐量优先)

4.1. ParNew(-XX:+UseParNewGC)

ParNew 回收器是在 Serial 回收器的基础上演化而来的,属于 Serial 回收器的 多线程版本,同样运行在 新生代区域。在实现上,两者共用很多代码。在不同运行环境下,根据 CPU 核数,开启 不同的线程数,从而达到 最优 的垃圾回收效果。对于那些 Server 模式的应用程序,如果考虑采用 CMS 作为 老生代回收器 时,ParNew 回收器是一个不错的选择。

ParNew 新生代回收器 采用的是 复制算法

4.2. Parallel Scavenge(-XX:+UseParallelGC)

和 ParNew 回收一样,Parallel Scavenge 回收器也是运行在 新生代区域,属于 多线程的回收器。但不同的是,ParNew 回收器是通过控制 垃圾回收 的 线程数 来进行参数调整,而 Parallel Scavenge 回收器更关心的是 程序运行的吞吐量。即一段时间内,用户代码运行时间占 总运行时间 的百分比。

Parallel Scavenge 新生代回收器 采用的是 复制算法

4.3. Parallel Old(-XX:+UseParallelOldGC)

Parallel Old 回收器是 Parallel Scavenge 回收器的 老生代版本,属于 多线程回收器,采用 标记-整理算法Parallel Old 回收器和 Parallel Scavenge 回收器同样考虑了 吞吐量优先 这一指标,非常适合那些 注重吞吐量 和 CPU 资源敏感 的场合。

Parallel Old 老年代回收器 采用的是 标记 - 整理算法

5. 其他的回收器(停顿时间优先)

5.1. CMS(-XX:+UseConcMarkSweepGC)

CMS(Concurrent Mark Sweep) 回收器是在 最短回收停顿时间 为前提的回收器,属于 多线程回收器,采用 标记-清除算法

相比之前的回收器,CMS 回收器的运作过程比较复杂,分为四步:

  1. 初始标记(CMS initial mark)

初始标记 仅仅是标记 GC Roots 内 直接关联 的对象。这个阶段 速度很快,需要 Stop the World

  1. 并发标记(CMS concurrent mark)

并发标记 进行的是 GC Tracing,从 GC Roots 开始对堆进行 可达性分析,找出 存活对象

  1. 重新标记(CMS remark)

重新标记 阶段为了 修正 并发期间由于 用户进行运作 导致的 标记变动 的那一部分对象的 标记记录。这个阶段的 停顿时间 一般会比 初始标记阶段 稍长一些,但远比 并发标记 的时间短,也需要 Stop The World

  1. 并发清除(CMS concurrent sweep)

并发清除 阶段会清除垃圾对象。

初始标记CMS initial mark)和 重新标记CMS remark)会导致 用户线程 卡顿,Stop the World 现象发生。

在整个过程中,CMS 回收器的 内存回收 基本上和 用户线程 并发执行,如下所示:

由于 CMS 回收器 并发收集停顿低,因此有些地方成为 并发低停顿回收器Concurrent Low Pause Sweep Collector)。

CMS 回收器的缺点:

  1. CMS回收器对CPU资源非常依赖

CMS 回收器过分依赖于 多线程环境,默认情况下,开启的 线程数 为(CPU 的数量 + 3)/ 4,当 CPU 数量少于 4 个时,CMS 对 用户查询 的影响将会很大,因为他们要分出一半的运算能力去 执行回收器线程

  1. CMS回收器无法清除浮动垃圾

由于 CMS 回收器 清除已标记的垃圾 (处于最后一个阶段)时,用户线程 还在运行,因此会有新的垃圾产生。但是这部分垃圾 未被标记,在下一次 GC 才能清除,因此被成为 浮动垃圾

由于 内存回收 和 用户线程 是同时进行的,内存在被 回收 的同时,也在被 分配。当 老生代中的内存使用超过一定的比例时,系统将会进行 垃圾回收;当 剩余内存 不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,临时采用 Serial Old 算法进行 清除,此时的 性能 将会降低。

  1. 垃圾收集结束后残余大量空间碎片

CMS 回收器采用的 标记清除算法,本身存在垃圾收集结束后残余 大量空间碎片 的缺点。CMS 配合适当的 内存整理策略,在一定程度上可以解决这个问题。

5.2. G1回收器(垃圾区域Region优先)

G1 是 JDK 1.7 中正式投入使用的用于取代 CMS 的 压缩回收器。它虽然没有在物理上隔断 新生代 与 老生代,但是仍然属于 分代垃圾回收器G1 仍然会区分 年轻代 与 老年代,年轻代依然分有 Eden 区与 Survivor 区。

G1 首先将  分为 大小相等 的 Region,避免 全区域 的垃圾回收。然后追踪每个 Region 垃圾 堆积的价值大小,在后台维护一个 优先列表,根据允许的回收时间优先回收价值最大的 Region。同时 G1采用 Remembered Set 来存放 Region 之间的 对象引用 ,其他回收器中的 新生代 与 老年代 之间的对象引用,从而避免 全堆扫描G1 的分区示例如下图所示:

这种使用 Region 划分 内存空间 以及有 优先级 的区域回收方式,保证 G1 回收器在有限的时间内可以获得尽可能 高的回收效率

G1 和 CMS 运作过程有很多相似之处,整个过程也分为 4 个步骤:

  1. 初始标记(CMS initial mark)

初始标记 仅仅是标记 GC Roots 内 直接关联 的对象。这个阶段 速度很快,需要 Stop the World

  1. 并发标记(CMS concurrent mark)

并发标记 进行的是 GC Tracing,从 GC Roots 开始对堆进行 可达性分析,找出 存活对象

  1. 重新标记(CMS remark)

重新标记 阶段为了 修正 并发期间由于 用户进行运作 导致的 标记变动 的那一部分对象的 标记记录。这个阶段的 停顿时间 一般会比 初始标记阶段 稍长一些,但远比 并发标记 的时间短,也需要 Stop The World

  1. 筛选回收

首先对各个 Region 的 回收价值 和 成本 进行排序,根据用户所期望的 GC 停顿时间 来制定回收计划。这个阶段可以与用户程序一起 并发执行,但是因为只回收一部分 Region,时间是用户可控制的,而且停顿 用户线程 将大幅提高回收效率。

与其它 GC 回收相比,G1 具备如下 4 个特点:

  • 并行与并发

使用多个 CPU 来缩短 Stop-the-World 的 停顿时间,部分其他回收器需要停顿 Java 线程执行的 GC 动作,G1 回收器仍然可以通过 并发的方式 让 Java 程序继续执行。

  • 分代回收

与其他回收器一样,分代概念 在 G1 中依然得以保留。虽然 G1 可以不需要 其他回收器配合 就能独立管理 整个GC堆,但它能够采用 不同的策略 去处理 新创建的对象 和 已经存活 一段时间、熬过多次 GC 的旧对象,以获取更好的回收效果。新生代 和 老年代 不再是 物理隔离,是多个 大小相等 的独立 Region

  • 空间整合

与 CMS 的 标记—清理 算法不同,G1 从 整体 来看是基于 标记—整理 算法实现的回收器。从 局部(两个 Region 之间)上来看是基于 复制算法 实现的。

但无论如何,这 两种算法 都意味着 G1 运作期间 不会产生内存空间碎片,回收后能提供规整的可用内存。这种特性有利于程序长时间运行,分配大对象 时不会因为无法找到 连续内存空间 而提前触发 下一次 GC

  • 可预测的停顿

这是 G1 相对于 CMS 的另一大优势,降低停顿时间 是 G1 和 CMS 共同的关注点。G1除了追求 低停顿 外,还能建立 可预测 的 停顿时间模型,能让使用者明确指定在一个 长度 为 M 毫秒的 时间片段 内,消耗在 垃圾回收 上的时间不得超过 N 毫秒。(后台维护的 优先列表,优先回收 价值大 的 Region)。

参考

周志明,深入理解Java虚拟机:JVM高级特性与最佳实践,机械工业出版社



欢迎关注技术公众号:零壹技术栈

零壹技术栈

本帐号将持续分享后端技术干货,包括虚拟机基础,多线程编程,高性能框架,异步、缓存和消息中间件,分布式和微服务,架构学习和进阶等学习资料和文章。

原文地址:https://www.cnblogs.com/ostenant/p/9696206.html

时间: 2024-10-29 21:24:51

JVM系列(六) - JVM垃圾回收器的相关文章

jvm系列(八):jvm知识点总览-高级Java工程师面试必备

在江湖中要练就绝世武功必须内外兼备,精妙的招式和深厚的内功,武功的基础是内功.对于武功低(就像江南七怪)的人,招式更重要,因为他们不能靠内功直接去伤人,只能靠招式,利刃上优势来取胜了,但是练到高手之后,内功就更主要了.一个内功低的人招式在奇妙也打不过一个内功高的人.比如,你剑法再厉害,一剑刺过来,别人一掌打断你的剑,你还怎么使剑法,你一掌打到一个武功高的人身上,那人没什么事,却把你震伤了,你还怎么打.同样两者也是相辅相成的,内功深厚之后,原来普通的一招一式威力也会倍增. 对于搞开发的我们其实也是

[转]JVM系列三:JVM参数设置、分析

[转]JVM系列三:JVM参数设置.分析 不管是YGC还是Full GC,GC过程中都会对导致程序运行中中断,正确的选择不同的GC策略,调整JVM.GC的参数,可以极大的减少由于GC工作,而导致的程序运行中断方面的问题,进而适当的提高Java程序的工作效率.但是调整GC是以个极为复杂的过程,由于各个程序具备不同的特点,如:web和GUI程序就有很大区别(Web可以适当的停顿,但GUI停顿是客户无法接受的),而且由于跑在各个机器上的配置不同(主要cup个数,内存不同),所以使用的GC种类也会不同(

JVM系列文章(二):垃圾回收机制

作为一个程序员,仅仅知道怎么用是远远不够的.起码,你需要知道为什么可以这么用,即我们所谓底层的东西. 那到底什么是底层呢?我觉得这不能一概而论.以我现在的知识水平而言:对于Web开发者,TCP/IP.HTTP等等协议可能就是底层:对于C.C++程序员,内存.指针等等可能就是底层的东西.那对于Java开发者,你的Java代码运行所在的JVM可能就是你所需要去了解.理解的东西. 我会在接下来的一段时间,和读者您一起去学习JVM,所有内容均参考自<深入理解Java虚拟机:JVM高级特性与最佳实践>(

JVM系列之五:垃圾回收

. jdk1.7的堆内存 1. 堆(Java堆) 堆是java虚拟机所管理的内存中最大的一块内存区域,也是被各个线程共享的内存区域, 在JVM启动时创建,该内存区域存放了对象实例(包括基本类型的变量及其值)及数组(所有new的对象). 但是并不是所有的对象都在堆上,由于栈上分配和标量替换,导致有些对象不在堆上. 其大小通过-Xms(最小值)和-Xmx(最大值)参数设置, 1. -Xms为JVM启动时申请的最小内存,默认为操作系统物理内存的1/64但小于1G, 2. -Xmx为JVM可申请的最大内

深入理解JVM虚拟机3:垃圾回收器详解

JVM GC基本原理与GC算法 微信公众号[Java技术江湖]一位阿里 Java 工程师的技术小站.作者黄小斜,专注 Java 相关技术:SSM.SpringBoot.MySQL.分布式.中间件.集群.Linux.网络.多线程,偶尔讲点Docker.ELK,同时也分享技术干货和学习经验,致力于Java全栈开发!(关注公众号后回复”Java“即可领取 Java基础.进阶.项目和架构师等免费学习资料,更有数据库.分布式.微服务等热门技术学习视频,内容丰富,兼顾原理和实践,另外也将赠送作者原创的Jav

JVM中的G1垃圾回收器

我们先回顾一下主流Java的垃圾回收器(HotSpot JVM).本文是针对堆的垃圾回收展开讨论的. 堆被分解为较小的三个部分.具体分为:新生代.老年代.持久代. 绝大部分新生成的对象都放在Eden区,当Eden区将满,JVM会因申请不到内存,而触发Young GC ,进行Eden区+有对象的Survivor区(设为S0区)垃圾回收,把存活的对象用复制算法拷贝到一个空的Survivor(S1)中,此时Eden区被清空,另外一个Survivor S0也为空.下次触发Young GC回收Eden+S

JVM系列(四) - JVM垃圾回收算法

前言 前面介绍了Java内存运行时区域,其中 程序计数器.虚拟机栈.本地方法栈 三个区域随线程而生,随线程而灭:栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作.每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知的,因此这几个区域的内存分配和回收都具备确定性.在这几个区域内不需要过多考虑回收的问题,因为方法结束或线程结束时,内存自然就跟随着回收了. Java堆 和 方法区 则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样.我们只

[JVM 相关] Java 新型垃圾回收器(Garbage First,G1)

回顾传统垃圾回收器 HotSpot 垃圾收集器实现 Serial Collector(串型收集器) 使用场景,大多数服务器是单核CPU. 适用收集场景:1. 新生代收集(Young Generation Collection)2. 老年代收集(Old Generation Collection) Parallel Conllector(并行收集器) 又叫吞吐量收集器(throughput collector)应用于多核系统. 适用收集场景:1. 新生代收集是并行处理.2. 老年代收集和Seria

JVM系列(七) - JVM线上监控工具

前言 通过上一篇的 JVM 垃圾回收知识,我们了解了 JVM 具体的 垃圾回收算法 和几种 垃圾回收器.理论是指导实践的工具,有了理论指导,定位问题的时候,知识和经验是关键基础,数据可以为我们提供依据. 在线上我们经常会遇见如下几个问题: 内存泄露: 某个进程突然 CPU 飙升: 线程死锁: 响应变慢. 如果遇到了以上这种问题,在 线下环境 可以有各种 可视化的本地工具 支持查看.但是一旦到 线上环境,就没有这么多的 本地调试工具 支持,我们该如何基于 监控工具 来进行定位问题? 我们一般会基于