概述

  如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。Java 虚拟机规范中对垃圾收集器应该如何实现没有任何规定,因此不同的厂商、不同版本的虚拟机所提供的垃圾处理器都可能会有很大差别,并且一般都会提供参数供用户根据自己的应用特点和要求组合出各个年代所使用的收集器。下图为 HotSpot 虚拟机的垃圾收集器:

  

  上图展示了7种用于不同分代的收集器,其中新生代的收集器有:Serial 、ParNew、Parallel Scavenge, 老年代的收集器有:Serial Old 、Parallel Old、CMS,新生代和老年代都可用的收集器: G1。如果两个收集器之间有连线,就说明它们可以搭配使用。

  虽然我们是在对各个收集器进行比较,但并非为了挑选出一个最好的收集器。因为直到现在为止还没有最好的收集器出现,更没有万能的收集器,所以我们选择的只有对具体应用最合适的收集器。

Serial 收集器

  Serial 收集器是最基本的、发展历史最悠久的收集器,曾经(在 JDK 1.3.1 之前)是虚拟机新生代收集的唯一选择。这个收集器是一个单线程的收集器,但它的 “单线程” 的意义并不仅仅说明它只会使用一个 CPU 或者一条线程去完成垃圾收集工作,更重要的是它在进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。“Stop The World” 这个名字也许听起来很酷,但这项工作实际上是虚拟机在后台自动发起和自动完成的,在用户不可见的情况下把用户正常工作的线程全部停掉,这对很多应用来说都是难以接受的。不妨想一下:要是你的计算机每运行一个小时就会暂停响应 5 分钟,你会有什么样的心情?下图示意了 Serial / Serial Old 收集器的运行过程。

  

  对于 “Stop The World” 带给用户的不良体验,虚拟机的设计者们表示完全理解,但也表示非常委屈:“你妈妈在给你打扫房间的时候,肯定也会让你老老实实地在椅子上或者房间外待着,如果她一边打扫,你一边乱扔纸屑,这房间还能打扫完吗?” 这确实是一个合情合理的矛盾,虽然收集器这项工作听起来和打扫房间属于一个性质的,但实际上肯定还要比打扫房间复杂得多啊!

  从 JDK 1.3 开始,到 JDK 1.7,HotSpot 虚拟机开发团队为消除或者减少工作线程因内存回收而导致停顿的努力一直在进行着,从 Serial 收集器到 Parallel 收集器,再到 CMS 收集器,乃至 GC 收集器的最前沿结果——G1收集器,我们看到了一个个越来越优秀,也越来越复杂的收集器出现,用户线程的停顿时间在不断缩短,但是仍然没有办法完全消除。寻找更优秀的GC 收集器的工作仍在继续。

  看到这里, Serial 收集器似乎是一个用处不大的收集器。实际上到目前为止,它依然是虚拟机运行在 Client 模式下的默认新生代收集器。它有着优于其他收集器的地方:简单而高效(与其他收集器的单线程比),对于限定单个 CPU 的环境来说,Serial 收集器优于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。在用户的桌面应用场景中,分配给虚拟机管理的内存一般来说不会太大,停顿时间完全可以控制在毫秒级,只要不是频繁发生,这点停顿是可以接受的。所以, Serial 收集器对于运行 Client 模式下的虚拟机来说是一个好的选择。

ParNew 收集器】 

  ParNew 收集器其实就是 Serial 收集器的多线程版本,除了使用多条线程进行垃圾收集之外,其余行为包括 Serial 收集器可用的所有参数、收集算法、Stop The World、对象分配规则、回收策略等都与 Serial 收集器完全一样,在实现上,这两种收集器也共用了相当多的代码。ParNew 收集器的工作过程如下:

  

  ParNew 收集器除了多线程收集之外,其他与 Serial 收集器相比并没有太多创新之处,但它却是许多运行在 Server 模式下的虚拟机中首选的新生代收集器,其中有一个与性能无关但很重要的原因是,除了 Serial 收集器外,目前只有它能与 CMS 收集器配合工作。在 JDK 1.5 时期, HotSpot 推出了一款在强交互应用几乎可认为有划时代意义的垃圾收集器——CMS 收集器(Concurrent Mark Sweep),这款收集器是 HotSpot 虚拟机中第一款真正意义上的并发(Concurrent)收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作,用前面那个例子的话来说,就是做到了在你妈妈打扫房间的时候你还能一边往地上扔纸屑。

  不幸的是,CMS 作为老年代的收集器,却无法与 JDK 1.4 中已经存在的新生代收集器 Parallel Scavenge 配合工作,所以在 JDK 1.5 使用 CMS 来收集老年代的时候,新生代只能选择 ParNew 或者 Serial 收集器中的一个。ParNew 收集器也是使用 -XX:+UseConcMarkSweepGC 选项后的默认新生代收集器,也可以使用 -XX:+UseParNewGC 选项来强制指定它。

  ParNew 收集器在单 CPU 的环境中绝对不会比 Serial 收集器有更好的效果,甚至由于存在线程交互的开销,该收集器在通过超线程技术实现的两个 CPU 的环境下都不能百分之百地保证可以超越 Serial 收集器。当然,随着可以使用的 CPU 的数量的增加,它对于 GC 时系统资源的有效利用还是很有好处的。它默认开启的收集线程数与 CPU 的数量相同,在 CPU 非常多(例如 32个,现在 CPU 动辄就 4 核加超线程,服务器超过 32 个逻辑 CPU 的情况越来越多了) 的环境下,可以使用 -XX: ParallelGCThreads 参数来限制垃圾收集的线程数。

Parallel Scavenge 收集器

  Parallel Scavenge 收集器是一个新生代收集器,它也是使用复制算法的收集器,又是并行的多线程收集器,看上去和 ParNew 都一样,那它有什么特别之处呢?

  Parallel Scavenge 收集器的特点是它的关注点与其他收集器不同, CMS 等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而 Parallel Scavenge 收集器的目标则是达到一个可控制的吞吐量(Throughput)。所谓吞吐量就是 CPU 用于运行用户代码的时间与 CPU 总消耗时间的比值,即 吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间),虚拟机总共运行了 100 分钟,其中垃圾收集花掉了 1分钟,那吞吐量就是 99%。

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

  Parallel Scavenge 收集器提供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间的 -XX:MaxGCPauseMills 参数以及直接设置吞吐量大小的 -XX:GCTimeRatio 参数。

  MaxGCPauseMills 参数允许的值是一个大于 0 的毫秒数,收集器将尽可能地保证内存回收花费的时间不超过设定值。不过大家不要认为如果把这个参数设置地稍小一点就可以使系统的垃圾收集速度变得更快,GC 停顿时间缩短是以牺牲吞吐量和新生代空间来换取的;系统把新生代调小一点,收集 300M 新生代肯定比收集 500M 快吧,这也直接导致垃圾收集发生地更频繁一些,原来 10 秒收集一次、每次停顿 100 毫秒,现在变成 5 秒收集一次,每次停顿 70 毫秒。停顿时间的确在下降,但吞吐量也降下来了。

  GCTimeRatio 参数的值应当是一个大于 0 且小于 100 的整数,也就是垃圾收集时间占总时间的比率,相当于是吞吐率的倒数。如果把此参数设置为 19,那就是允许最大 GC 时间就占总时间的 5%(即 1/(1+19)),默认值为 99,就是允许最大 1%(1/(1+99))的垃圾收集时间。

  由于与吞吐量关系密切,Parallel Scavenge 收集器也经常被称为 “吞吐量优先” 的收集器。除上述两个参数之外,Parallel Scavenge 收集器还有一个参数 -XX:UseAdaptiveSizePolicy 值得关注。这是一个开关参数,当这个参数打开之后,就不需要手工指定新生代的大小(-Xmn)、Eden 与 Survivor 区的比例(-XX:SurvivorRatio)、晋升老年代对象大小(-XX:PretenureSizeThreshold)等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量,这种调节方式被称为 GC 自适应的调节策略(GC Ergonomics)。如果对于收集器运作原理不太了解,手工优化存在困难的时候,使用 Parallel Scavenge 收集器配合自适应调节策略,把内存管理的调优任务交给虚拟机去完成将是一个不错的选择。只需要把基本的内存数据设置好(如 -Xmx 设置最大堆内存,-Xms 设置初始堆内存),然后使用 MaxGCPauseMills(更关注最大停顿时间)或者 GCTimeRatio (更关注吞吐量)参数给虚拟机设立一个优化目标,那具体细节参数的调节工作就由虚拟机完成了。自适应调节策略也是 Parallel Scavenge 与 ParNew 收集器的一个重要区别。

Serial Old 收集器

  Serial Old 收集器是 Serial 收集器的老年代版本,它同样是一个单线程收集器,使用 “标记-整理” 算法。这个收集器的主要意义也是在于给 Client 模式下的虚拟机使用。如果在 Server 模式下,那么它主要还有两大用途:一种用途是在 JDK 1.5 以及之前的版本中与 Parallel Scavenge 收集器搭配使用,另一种用途就是作为 CMS 收集器的后备预案,在并发手机发生 Concurrent Mode Failure 时使用。

Parallel Old 收集器

  Parallele Old 是 Parallel Scavenge 收集器的老年代版本,使用多线程和 “标记-整理”算法。这个收集器是在 JDK 1.6 中才开始提供的,在此之前,新生代的 Parallel Scavenge 收集器一直处于比较尴尬的状态。原因是,如果新生代选择了 Parallel Scavenge 收集器,老年代除了 Serial Old 收集器外别无选择(Parallele Scavenge 收集器无法与 CMS 收集器配合使用)。由于老年代 Serial Old 收集器在 Server 模式下应用性能上的 “拖累”, 使用 Parallel Scavenge 收集器也未必能在整体性能应用上获得吞吐量最大化的效果,由于单线程的老年代收集中无法充分利用服务器多 CPU 的处理能力,在老年代很大而且硬件比较高级的环境中,这种组合的吞吐量甚至还不一定有 ParNew 加 CMS 的组合 “给力”。

  直到 Parallel Old 收集器出现后,“吞吐量优先” 收集器终于有了比较名副其实的应用组合,在注重吞吐量以及 CPU 资源敏感的场合,都可以优先考虑 Parallel Scavenge 加 Parallel Old 收集器。

CMS 收集器

G1 收集器】 

A7. JVM 垃圾回收收集器(GC 收集器)的更多相关文章

  1. A6. JVM 垃圾回收算法(GC 算法)

    [概述] 常见的垃圾回收算法有:标记-清除算法.复制算法.标记-整理算法.分代收集算法. [标记-清除算法] 标记-清除算法是最基础的收集算法,如同它的名字一样,算法分为 “标记” 和 “清除” 两个 ...

  2. 一 JVM垃圾回收模型

    一 JVM垃圾回收模型 一. GC算法 1.1 标记-清除算法(Mark-Sweep) 算法分为"标记"和"清除"两个阶段首先标记出所有需要回收的对象,然后回收 ...

  3. JVM垃圾回收之CMS收集器

    从前文JVM垃圾回收几种常见算法和常见收集器我们知道,CMS是老年代垃圾收集器.CMS 收集器主要关注系统停顿时间.CMS 是 Concurrent Mark Sweep 的缩写,意为并发标记清除,从 ...

  4. JVM垃圾回收机制总结(3) :按代垃圾收集器

    为什么要分代 分代的垃圾回收策略,是基于这样一个事实:不同的对象的生命周期是不一样的 . 因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率. 在Java程序运行的过程中,会产生大量的对 ...

  5. JVM(三) 垃圾回收时间点和垃圾收集器

      收集器组合章节来自第一篇参考文章,非原创,作者总结地非常好!          分代收集相关概念来自参考文章第二篇,非原创         第二篇参考资料的文章质量很高,推荐阅读! 分代收集(Ge ...

  6. Java:JVM垃圾回收(GC)机制

    JVM垃圾回收算法 1.标记清除(Mark-Sweep) 原理: 从根集合节点进行扫描,标记出所有的存活对象,最后扫描整个内存空间并清除没有标记的对象(即死亡对象)适用场合: 存活对象较多的情况下比较 ...

  7. .Net平台GC VS JVM垃圾回收

    前言 不知道你平时是否关注程序内存使用情况,我是关注的比较少,正好借着优化本地一个程序的空对比了一下.Net平台垃圾回收和jvm垃圾回收,顺便用dotMemory看了程序运行后的内存快照,生成内存快照 ...

  8. JVM垃圾回收(GC)

    JVM垃圾回收(GC) 1. 判断对象是否可以被回收 引用计数法:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收.此方法简单,但无法解决对象相互循环引用的问 ...

  9. JVM垃圾回收(GC)整理总结学习

    基本回收算法 1. 引用计数(Reference Counting)比较古老的回收算法.原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数.垃圾回收时,只用收集计数为0的对象.此算法最 ...

随机推荐

  1. TCP 拆、粘包

    Netty(三) 什么是 TCP 拆.粘包?如何解决? 前言 记得前段时间我们生产上的一个网关出现了故障. 这个网关逻辑非常简单,就是接收客户端的请求然后解析报文最后发送短信. 但这个请求并不是常见的 ...

  2. [Usaco2009 MAR] Earthquake Damage 2

    [题目链接] https://www.lydsy.com/JudgeOnline/problem.php?id=1585 [算法] 一个最小割的经典模型 , 详见代码 时间复杂度 : O(dinic( ...

  3. 关于bitset

    https://www.zybuluo.com/ysner/note/1327705 原理 \(bitset\)的原理是将一大堆值为\(0/1\)的数压成一个数. 操作 通过\(i>>x\ ...

  4. vue 子组件 索引 ref

    尽管有props和events,但是有时任然需要在javascript中直接访问子组件.为此可是使用ref为子组件指定一个索引ID. <div id="parent"> ...

  5. 摘抄 - linux 目录结构简介

    /   根目录 |—–/bin   软连接,指向 /usr/bin.存储一些命令,一般为用户命令 |—-/boot  系统启动相关的文件;包括启动时内核的一些配置,grub配置等等:一般为之分配300 ...

  6. EF7学习资料整理

    EntityFramework 7 开发纪录 http://www.cnblogs.com/xishuai/archive/2014/11/28/ef7-develop-note.html Entit ...

  7. HDU 5514 Frogs (数论容斥)

    题意:有n只青蛙,m个石头(围成圆圈).第i只青蛙每次只能条ai个石头,问最后所有青蛙跳过的石头的下标总和是多少? 析:首先可以知道的是第 i 只青蛙可以跳到 k * gcd(ai, m),然后我就计 ...

  8. bzoj 5281: [Usaco2018 Open]Talent Show【dp】

    注意到sum_t比较小,所以设f[i][j]为选前i头牛,当前sum_t为j的最小sum_w值,转移是f[i][j]=min(f[i-1][j],f[i-1][j-t[i]]+w[i]),然后i维用滚 ...

  9. AOP日志框架实现

    AOP日志框架实现 JDK动态代理实现日志框架 首先,在项目包com.ay.test 下创建业务接口类BusinessClassService,具体代码如下: BusinessC lassServic ...

  10. 面试基础试题 一 http和HTTPS的区别

    作为老生常谈的问题,我主要给出我自己的理解和结合大牛的叙述的综合看法,来检验自己学习 超文本传输协议HTTP协议被用于在Web浏览器和网站服务器之间传递信息,HTTP协议以明文方式发送内容,不提供任何 ...