虚拟机的垃圾回收器,没有哪一个是绝对好的,只有比较好的。
今天的这篇文章,我要与你分享虚拟机的那些垃圾回收器们。内容不多,可以耐心看完。
之前我们已经了解过具体的 GC 涉及的细节知识点,现在我们站在更高的角度,来看看各种垃圾收集器,以及其中两个的工作过程(CMS 和 G1)。
我们知道,虚拟机将保存对象实例的区域分为了两个叫做 新生代 和 老年代 的地方,为此虚拟机针对不同的内存区域利用不同的算法设计了不同的垃圾收集器。
了解垃圾收集器之前,我觉得还是有必要在复习一下 Stop The World ,其用来形容在安全点用户线程暂停的这种状态的一个叫法。关于这个垃圾收集器工作的时候为什么要 Stop The World 还有一个比较有意思的事 ,“你妈妈在给你打扫房间的时候, 肯定也会让你老老实实地在椅子上或者房间外待着, 如果她一边打扫, 你一边乱扔纸屑, 这房间还能打扫完? ”这是虚拟机团队对 Stop The World 的说明,嗯,很有道理,哈哈哈。
关键字:新生代、Stop The World 、标记 - 复制算法、单线程
Serial 是作用在新生代的垃圾收集器,单线程工作,在工作的时候需要 Stop The World,(包括之前提到的 GC 工作,都是指可达性分析)。采用的是标记 - 复制算法,(关于标记 - 复制的内容之前有讲过就不再展开说了)。
看起来 Serial 收集器没什么特别的,但实际上 Serial 收集器在一些特殊的场景下有着不错的表现,这些要得益于他的额外内存消耗,因为其相比其他收集器要小一些,所以在服务器资源受限的情况下(单核或较少核心以及内存紧张),这个简单的单线程收集器效率还是很可观的。
关键字:新生代、Stop The World 、标记 - 复制算法、多线程、绝配
ParNew 可以看成 new parallel gc ,一个新的并行垃圾收集器。是一个对标 Serial 的收集器,它与 Serial 的区别就是它在工作的时候是使用多线程进行工作的。还有,它是目前(JDK 9 以后)唯一一个能配合 CMS 在新生代工作的垃圾收集器。
关键字:新生代、Stop The World、标记 - 复制算法、多线程、可控吞吐量【用户线程执行时间 /(用户线程执行时间+GC线程执行时间)】、自适应策略
与 ParNew 相比,Parallel Scavenge 多了一些额外的功能,停顿时间和吞吐量可控(通过参数配置-XX: MaxGCPauseMillis 单位毫秒,控制每次垃圾回收的最大停顿时间。-XX: GCTimeRatio 控制吞吐量 0 - 100 整数,1/(1+设置的参数) = 垃圾收集时间占程序执行的总时间比率。
对于 Parallel Scavenge 还有一个特点,就是自适应策略,把内存管理工作完全交由虚拟机,通过参数 -XX: +UseAdaptiveSizePolicy 启用自适应内存策略,这样就不需要指定内存参数,比如新生代大小、eden survivor 比例 晋升年龄等参数。虚拟机会根据当前系统运行状态动态的调整,达到一个合适的停顿时间和吞吐量。
上面这三种 Serial 、 ParNew 、 Parallel Scavenge 都是新生代的垃圾收集器,下面我们来看看老年代的垃圾收集器。
关键字:老年代、Stop The World、标记 - 整理算法、单线程、替补
Serial Old 是和 Serial 一样的一款收集器,只不过,它是工作在老年代的。换句话说就是 Serial Old 就是 Serial 的老年代版本。Serial Old 有一个特殊的用途就是作为 CMS 并发清除失败的时候的替补,这里后面 CMS 收集器再看。
关键字:老年代、Stop The World、标记 - 整理算法、多线程、CP(组合)
Parallel Old 是一个多线程并行的老年代垃圾收集器。它的出现也是为了解决吞吐量最大化的问题。因为他没出现之前,只有 Serial Old 一款老年代垃圾收集器来配合 Parallel Scavenge ,因为 Serial Old 的单线程性能原因,导致 Parallel Scavenge 吞吐量的优势体现不出来。直到它的出现,它与 Paralle Scavenge 就组成了一对完美的吞吐量 CP。
关键字:老年代、Stop The World、标记 - 清除算法、多线程、短停顿
Concurrent Mark Sweep 的设计初衷就是要停顿的时间最短!JDK 5 开始使用,JDK 9 以前最优秀,为啥是 JDK 9 以前最优秀,因为 JDK 9 发布的时候,默认启用了 G1 收集器,同时你如果手动改成 CMS 的话,会受到一个 CMS 被声明为不推荐的警告,下面是它具体的工作过程,一共经历 4 个阶段
具体步骤如下:
CMS 虽然有了一个较短的停顿时间,但是也有一些其他随之而来的问题。
优点:
缺点:
关键字:里程碑、JDK 9、区域管理、按需回收、延迟可控的最高吞吐量
要说 CMS 是一个划时代收集器,那 G1 可以称得上划时代的划时代收集器,其作为 JVM 的垃圾收集器的里程碑是有一定原因的,我们继续往下看。G1 出现的原因也很简单,那就是替换掉 CMS 。G1的设计是颠覆性的设计思路,它跳出了内存一定要划分新生代老年代的这个枷锁,它的工作模式为 Mixed Mode。并且 JDK 9 的时候开始启用,成为了服务端模式下的默认垃圾收集器,替换掉了原来的吞吐量组合(Parallel Scavenge + Parallel Old),同时 CMS 被声明为不推荐使用,CMS 也是从JDK 9 开始准备退役。按需回收说的是 G1 在做清理的时候,是依据一个可预测停顿时间模型来做的,这是个什么东西呢?简单来说就是,在清理之前,G1 对每个待回收的区域根据回收价值和时间进行排序,然后根据用户所期望的停顿时间来做一个最优回收,后面会继续说。
多了解一点,关于 G1 的工作模式,Mixed Mode 的扩展:G1 有纯 GC 模式和分代回收模式,分代模式会分为 Minor GC 和 Mixed GC 两种,这里的模式选择会影响最后的筛选回收阶段的回收集合的内容。这块内容可参考后面留的 R大 的链接
上面有一点展开说一下,就是 G1 不是没有这分代这种操作了吗?是通过内存区域来管理垃圾的,但是事实上 G1 将内存分成多个大小相等的 Region(区域) ,这些 Region 都可以作为Eden、Survivor、Humongous(Humongous 同老年代的作用)。
下面我们一起了解一下 G1 工作的具体步骤:
初始标记:Stop The World
并发标记:
最终标记:Stop The World
筛选回收:Stop The World
这个阶段会更新 Region 的统计数据,对每个 Region 根据其回收价值和成本进行排序,然后根据用户所期望的停顿时间(参数设置)来制定一个回收计划。再根据这个计划,选择任意 Region 来组成一个回收集(collection set)。将回收集中的 Region(被选中的区域) 中的存活对象复制到空的 Region 中,然后将旧的(选中的) Region 清理掉。
以上过程由多线程并行完成,同时因为移动对象需要暂停用户线程(Stop The World)
TAMS:Top at Mark Start Region 中的两个指针名称,他们的作用是将 Region 的一部分空间划分出来给并发回收过程中程序运行产生的新对象使用
SATB:原始快照,还记得之前我们对漏标的解决方案吗?一种是增量更新(CMS 采用的这种方案),另外一个就是原始快照,这里可以翻翻之前的内容。
G1除了并发标记阶段都需要暂停用户线程
G1的理想目标:在延迟可控的情况下达到最大的吞吐量
用户可以通过参数设置所期望的停顿时间,这个时间一般建议设置为 100 ~ 300 ms。
上面一共说了 7 款垃圾收集器,不过他们的具体使用我觉得有必要了解一下。
按照年代划分
新生代:Serial 、ParNew、Parallel Scavenge
老年代:Serial Old、Parallel Old、CMS
内存区域:G1
搭配组合
因为不同的阶段,垃圾收集器之间的搭配不同,所以我们就按照 JDK 9 作为划分界线,来看下 JDK 9 前后的搭配情况
JDK 9 以前
JDK 9 之后
通过搭配关系我们可以看出,JDK 9 以前,Hotspot 提供了多种选择,而且场面看起来很和谐,解释一下 CMS 与 Serial Old 之间的虚线,这代表 CMS 并发清除失败的时候,以 Serial Old 作为备选方案的组合。
JDK 9 之后,因为 G1 的出现,hotspot 取消了两种方案的支持(Serial + CMS 和 ParNew + CMS),仅提供了 4 种虚拟机搭配方案,他们分别是
看起来这 4 组搭配很完美,不过因为 G1 的出现,看起来的美好也没那么好了 G1在 server 模式下取代了高吞吐量的 CP (Parallel Scavenge + Parallel Old)成为了默认的垃圾收集器,同时在 JDK 9 使用 ParNew + CMS 这组搭配时,还会收到来自 hotspot 的警告,CMS 已经被声明为不推荐使用,因为 ParNew 此时只能与 CMS 搭配使用,所以可以说当时 CMS 拯救了 ParNew 的尴尬局面(当时新生代高性能的 ParNew 只能选择拖后腿的 Serial Old 一起工作),现在 ParNew 也要陪着 CMS 一起下岗了。综上所述,JDK 9 还剩下建议使用的组合
所以通过上面的分析,也能看出来 HotSpot 的用意,在 JDK 9 以后就是要将 G1 作为一个全能型的垃圾收集器来发展。
上面总结了截止 JDK 9 的垃圾收集器内容,其实对于垃圾收集器还有很多内容,比如 Shenandoah ,一个由 Red Hat 开发的低延迟垃圾收集器,还有 Oracle 后面的 ZGC。这两个垃圾收集器都采用了更加优秀的思想和实现方案。不过因为我没有对其深入的了解,所以在这就不再多说了。如果你对垃圾收集器的具体算法仍感兴趣,推荐访问下面这个链接,R大 写的虚拟机相关内容
https://www.iteye.com/blog/rednaxelafx-362738