美文网首页
《深入理解java虚拟机》笔记3——7种垃圾收集器

《深入理解java虚拟机》笔记3——7种垃圾收集器

作者: Meandni | 来源:发表于2019-01-11 12:40 被阅读5次

    2019大三的寒假计划——利用在公司每天早起的时间读书,第一本是周志明老师的《深入理解Java虚拟机——JVM高级特性与最佳实践》,这一系列是通过对原文的拜读与自己理解加上网络上的资料文章整理出的读书笔记。

    如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。**Java虚拟机规范中对垃圾收集器应该如何实现并没有任何规定,因此不同的厂商、版本的虚拟机所提供的垃圾收集器都可能会有很大差别,并且一般都会提供参数供用户根据自己的应用特点和要求组合出各个年代所使用的收集器。接下来讨论的收集器基于JDK1.7 Update 14 之后的HotSpot虚拟机(在此版本中正式提供了商用的G1收集器,之前G1仍处于实验状态),该虚拟机包含的所有收集器如下图所示:

    <div align="center"> <img src="http://pic.yupoo.com/meandni/e8793494/27df7474.jpg" width=""/> </div>

    以上是 HotSpot 虚拟机中的 7 个垃圾收集器,连线表示垃圾收集器可以配合使用。

    • 单线程与多线程:单线程指的是垃圾收集器只使用一个线程进行收集,而多线程使用多个线程;
    • 串行与并行:串行指的是垃圾收集器与用户程序交替执行,这意味着在执行垃圾收集的时候需要停顿用户程序;并行指的是垃圾收集器和用户程序同时执行。除了 CMS 和 G1 之外,其它垃圾收集器都是以串行的方式执行。

    相关概念

    并行和并发

    • 并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
    • 并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行。而垃圾收集程序运行在另一个CPU上。

    吞吐量(Throughput)

    吞吐量就是CPU用于运行用户代码的时间CPU总消耗时间的比值,即

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

    假设虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。

    Minor GC 和 Full GC

    • 新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。具体原理见上一篇文章。
    • 老年代GC(Major GC / Full GC):指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GC(但非绝对的,在Parallel Scavenge收集器的收集策略里就有直接进行Major GC的策略选择过程)。Major GC的速度一般会比Minor GC慢10倍以上。

    各收集器阐述

    1. Serial 收集器

    <div align="center"> <img src="http://pic.yupoo.com/meandni/5c861f07/b1ac068e.jpg" width=""/> </div>

    Serial 翻译为串行,也就是说它以串行的方式执行。

    它是单线程的收集器,只会使用一个线程进行垃圾收集工作。

    它的优点是简单高效,对于单个 CPU 环境来说,由于没有线程交互的开销,因此拥有最高的单线程收集效率。

    它是 Client 模式下的默认新生代收集器,因为在该应用场景下,分配给虚拟机管理的内存一般来说不会很大。Serial 收集器收集几十兆甚至一两百兆的新生代停顿时间可以控制在一百多毫秒以内,只要不是太频繁,这点停顿是可以接受的。

    2. ParNew 收集器

    <div align="center"> <img src="http://pic.yupoo.com/meandni/a5c6e277/481e1ca1.jpg" width=""/> </div>

    它是 Serial 收集器的多线程版本。

    是 Server 模式下的虚拟机首选新生代收集器,除了性能原因外,主要是因为除了 Serial 收集器,只有它能与 CMS 收集器配合工作。

    默认开启的线程数量与 CPU 数量相同,可以使用 -XX:ParallelGCThreads 参数来设置线程数。

    3. Parallel Scavenge 收集器

    与 ParNew 一样是多线程收集器。

    其它收集器关注点是尽可能缩短垃圾收集时用户线程的停顿时间,而它的目标是达到一个可控制的吞吐量,它被称为“吞吐量优先”收集器。这里的吞吐量指 CPU 用于运行用户代码的时间占总时间的比值。

    停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验。而高吞吐量则可以高效率地利用 CPU 时间,尽快完成程序的运算任务,适合在后台运算而不需要太多交互的任务。

    缩短停顿时间是以牺牲吞吐量和新生代空间来换取的:新生代空间变小,垃圾回收变得频繁,导致吞吐量下降。

    可以通过一个开关参数打开 GC 自适应的调节策略(GC Ergonomics),就不需要手工指定新生代的大小(-Xmn)、Eden 和 Survivor 区的比例、晋升老年代对象年龄等细节参数了。虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量。

    4. Serial Old 收集器

    <div align="center"> <img src="http://pic.yupoo.com/meandni/14f72c5c/367020d3.jpg" width=""/> </div>

    是 Serial 收集器的老年代版本,也是给 Client 模式下的虚拟机使用。如果用在 Server 模式下,它有两大用途:

    • 在 JDK 1.5 以及之前版本(Parallel Old 诞生以前)中与 Parallel Scavenge 收集器搭配使用。
    • 作为 CMS 收集器的后备预案,在并发收集发生 Concurrent Mode Failure 时使用。

    5. Parallel Old 收集器

    <div align="center"> <img src="http://pic.yupoo.com/meandni/2d1989e5/6e965e83.jpg" width=""/> </div>

    是 Parallel Scavenge 收集器的老年代版本。

    在注重吞吐量以及 CPU 资源敏感的场合,都可以优先考虑 Parallel Scavenge 加 Parallel Old 收集器。

    6. CMS 收集器

    <div align="center"> <img src="http://pic.yupoo.com/meandni/14f72c5c/367020d3.jpg" width=""/> </div>

    CMS(Concurrent Mark Sweep),Mark Sweep 指的是标记 - 清除算法。

    分为以下四个流程:

    • 初始标记:仅仅只是标记一下 GC Roots 能直接关联到的对象,速度很快,需要停顿。
    • 并发标记:进行 GC Roots Tracing 的过程,它在整个回收过程中耗时最长,不需要停顿。
    • 重新标记:为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,需要停顿。
    • 并发清除:不需要停顿。

    在整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,不需要进行停顿。

    具有以下缺点:

    • 吞吐量低:低停顿时间是以牺牲吞吐量为代价的,导致 CPU 利用率不够高。
    • 无法处理浮动垃圾,可能出现 Concurrent Mode Failure。浮动垃圾是指并发清除阶段由于用户线程继续运行而产生的垃圾,这部分垃圾只能到下一次 GC 时才能进行回收。由于浮动垃圾的存在,因此需要预留出一部分内存,意味着 CMS 收集不能像其它收集器那样等待老年代快满的时候再回收。如果预留的内存不够存放浮动垃圾,就会出现 Concurrent Mode Failure,这时虚拟机将临时启用 Serial Old 来替代 CMS。
    • 标记 - 清除算法导致的空间碎片,往往出现老年代空间剩余,但无法找到足够大连续空间来分配当前对象,不得不提前触发一次 Full GC。

    7. G1 收集器

    G1(Garbage-First),它是一款面向服务端应用的垃圾收集器,在多 CPU 和大内存的场景下有很好的性能。HotSpot 开发团队赋予它的使命是未来可以替换掉 CMS 收集器。

    堆被分为新生代和老年代,其它收集器进行收集的范围都是整个新生代或者老年代,而 G1 可以直接对新生代和老年代一起回收。

    <div align="center"> <img src="http://pic.yupoo.com/meandni/400d294e/1d022e13.png" width="600"/> </div>

    G1 把堆划分成多个大小相等的独立区域(Region),新生代和老年代不再物理隔离。

    <div align="center"> <img src="http://pic.yupoo.com/meandni/5393369d/5f8cbd42.png" width="600"/> </div>

    通过引入 Region 的概念,从而将原来的一整块内存空间划分成多个的小空间,使得每个小空间可以单独进行垃圾回收。这种划分方法带来了很大的灵活性,使得可预测的停顿时间模型成为可能。通过记录每个 Region 垃圾回收时间以及回收所获得的空间(这两个值是通过过去回收的经验获得),并维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region。

    每个 Region 都有一个 Remembered Set,用来记录该 Region 对象的引用对象所在的 Region。通过使用 Remembered Set,在做可达性分析的时候就可以避免全堆扫描。

    <div align="center"> <img src="http://pic.yupoo.com/meandni/ce8ef9fc/10c8999f.jpg" width=""/> </div>

    如果不计算维护 Remembered Set 的操作,G1 收集器的运作大致可划分为以下几个步骤:

    • 初始标记
    • 并发标记
    • 最终标记:为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程的 Remembered Set Logs 里面,最终标记阶段需要把 Remembered Set Logs 的数据合并到 Remembered Set 中。这阶段需要停顿线程,但是可并行执行。
    • 筛选回收:首先对各个 Region 中的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来制定回收计划。此阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分 Region,时间是用户可控制的,而且停顿用户线程将大幅度提高收集效率。

    具备如下特点:

    • 空间整合:整体来看是基于“标记 - 整理”算法实现的收集器,从局部(两个 Region 之间)上来看是基于“复制”算法实现的,这意味着运行期间不会产生内存空间碎片。
    • 可预测的停顿:能让使用者明确指定在一个长度为 M 毫秒的时间片段内,消耗在 GC 上的时间不得超过 N 毫秒。

    总结

    收集器 串行、并行or并发 新生代/老年代 算法 目标 适用场景
    Serial 串行 新生代 复制算法 响应速度优先 单CPU环境下的Client模式
    Serial Old 串行 老年代 标记-整理 响应速度优先 单CPU环境下的Client模式、CMS的后备预案
    ParNew 并行 新生代 复制算法 响应速度优先 多CPU环境时在Server模式下与CMS配合
    Parallel Scavenge 并行 新生代 复制算法 吞吐量优先 在后台运算而不需要太多交互的任务
    Parallel Old 并行 老年代 标记-整理 吞吐量优先 在后台运算而不需要太多交互的任务
    CMS 并发 老年代 标记-清除 响应速度优先 集中在互联网站或B/S系统服务端上的Java应用
    G1 并发 both 标记-整理+复制算法 响应速度优先 面向服务端应用,将来替换CMS

    参考资料

    原文博客:

    《深入理解java虚拟机》笔记3——7种垃圾收集器

    相关文章

      网友评论

          本文标题:《深入理解java虚拟机》笔记3——7种垃圾收集器

          本文链接:https://www.haomeiwen.com/subject/mrqxdqtx.html