《别总说CMS、G1该聊聊ZGC了》由会员分享,可在线阅读,更多相关《别总说CMS、G1该聊聊ZGC了(16页珍藏版)》请在金锄头文库上搜索。
1、在垃圾收集执行的时刻,应用程序需要暂停运行。可以串行收集,也可以并行收集。如果能做到并发收集(应用程序不必暂停),那绝对是很妙的事情。如果收集行为可控,那也是很妙的事情。CMS和G1作为垃圾收集器里的大杀器,是需要好好弄明白的,而且面试中也经常被问到,但对于ZGC却很少被提及。希望大家带着下面的问题进行阅读,有目标的阅读,收获更多:为什么没有一种牛逼的收集器像银弹一样适配所有场景?CMS的优点、缺点、适用场景?为什么CMS只能用作老年代收集器,而不能应用在新生代的收集?G1的优点、缺点、适用场景?ZGC的由来?1 CMS收集器CMS(Concurrent Mark Sweep)收集器是一种以获
2、取最短回收停顿时间为目标的收集器。这是因为CMS收集器工作时,GC工作线程与用户线程可以并发执行,以此来达到降低收集停顿时间的目的。CMS收集器仅作用于老年代的收集,是基于标记-清除算法的,它的运作过程分为4个步骤:初始标记(CMS initial mark)并发标记(CMS concurrent mark)重新标记(CMS remark)并发清除(CMS concurrent sweep)其中,初始标记、重新标记这两个步骤仍然需要Stop-the-world。初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,并发标记阶段就是进行GC Roots Tracing的过程,而重
3、新标记阶段则是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始阶段稍长一些,但远比并发标记的时间短。CMS以流水线方式拆分了收集周期,将耗时长的操作单元保持与应用线程并发执行。只将那些必需STW才能执行的操作单元单独拎出来,控制这些单元在恰当的时机运行,并能保证仅需短暂的时间就可以完成。这样,在整个收集周期内,只有两次短暂的暂停(初始标记和重新标记),达到了近似并发的目的。CMS收集器优点:并发收集、低停顿。CMS收集器缺点:CMS收集器对CPU资源非常敏感。CMS收集器无法处理浮动垃圾(Floating Garbage)。CMS
4、收集器是基于标记-清除算法,该算法的缺点都有(内存碎片)。停顿时间是不可预期的。CMS收集器之所以能够做到并发,根本原因在于采用基于“标记-清除”的算法并对算法过程进行了细粒度的分解。前面篇章介绍过标记-清除算法将产生大量的内存碎片这对新生代来说是难以接受的,因此新生代的收集器并未提供CMS版本。备注:说CMS是老年代收集器,其实不是非常准确。CMS 的各个收集过程其实是一个涉及年轻代和老年代的综合性垃圾回收器,在很多文章和书籍的划分中,都将 CMS 划分为了老年代垃圾回收器,加上它主要作用于老年代,所以一般误认为是。另外要补充一点,JVM在暂停的时候,需要选准一个时机。由于JVM系统运行期间
5、的复杂性,不可能做到随时暂停,因此引入了安全点的概念。安全点(Safepoint)安全点,即程序执行时并非在所有地方都能停顿下来开始GC,只有在到达安全点时才能暂停。Safepoint的选定既不能太少以至于让GC等待时间太长,也不能过于频繁以致于过分增大运行时的负荷。安全点的初始目的并不是让其他线程停下,而是找到一个稳定的执行状态。在这个执行状态下,Java虚拟机的堆栈不会发生变化。这么一来,垃圾回收器便能够“安全”地执行可达性分析。只要不离开这个安全点,Java虚拟机便能够在垃圾回收的同时,继续运行这段本地代码。程序运行时并非在所有地方都能停顿下来开始GC,只有在到达安全点时才能暂停。安全点
6、的选定基本上是以程序“是否具有让程序长时间执行的特征”为标准进行选定的。“长时间执行”的最明显特征就是指令序列复用,例如方法调用、循环跳转、异常跳转等,所以具有这些功能的指令才会产生Safepoint。对于安全点,另一个需要考虑的问题就是如何在GC发生时让所有线程(这里不包括执行JNI调用的线程)都“跑”到最近的安全点上再停顿下来。两种解决方案:抢先式中断(Preemptive Suspension)抢先式中断不需要线程的执行代码主动去配合,在GC发生时,首先把所有线程全部中断,如果发现有线程中断的地方不在安全点上,就恢复线程,让它“跑”到安全点上。现在几乎没有虚拟机采用这种方式来暂停线程从而
7、响应GC事件。主动式中断(Voluntary Suspension)主动式中断的思想是当GC需要中断线程的时候,不直接对线程操作,仅仅简单地设置一个标志,各个线程执行时主动去轮询这个标志,发现中断标志为真时就自己中断挂起。轮询标志的地方和安全点是重合的,另外再加上创建对象需要分配内存的地方。安全区域指在一段代码片段中,引用关系不会发生变化。在这个区域中任意地方开始GC都是安全的。也可以把Safe Region看作是被扩展了的Safepoint。2 G1收集器G1重新定义了堆空间,打破了原有的分代模型,将堆划分为一个个区域。这么做的目的是在进行收集时不必在全堆范围内进行,这是它最显著的特点。区域
8、划分的好处就是带来了停顿时间可预测的收集模型:用户可以指定收集操作在多长时间内完成。即G1提供了接近实时的收集特性。G1 的主要关注点在于达到可控的停顿时间,在这个基础上尽可能提高吞吐量。G1 使用了停顿预测模型来满足用户指定的停顿时间目标,并基于目标来选择进行垃圾回收的区块数量。G1 采用增量回收的方式,每次回收一些区块,而不是整堆回收。要清楚 G1 不是一个实时收集器(只是接近实时),它会尽力满足我们的停顿时间要求,但也不是绝对的,它基于之前垃圾收集的数据统计,估计出在用户指定的停顿时间内能收集多少个区块。G1与CMS的特征对比如下:特征G1CMS并发和分代是是最大化释放堆内存是否低延时是
9、是吞吐量高低压实是否可预测性强弱新生代和老年代的物理隔离否是G1具备如下特点:并行与并发:G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU来缩短Stop-the-world停顿的时间,部分其他收集器原来需要停顿Java线程执行的GC操作,G1收集器仍然可以通过并发的方式让Java程序继续运行。分代收集空间整合:与CMS的标记-清除算法不同,G1从整体来看是基于标记-整理算法实现的收集器,从局部(两个Region之间)上来看是基于“复制”算法实现的。但无论如何,这两种算法都意味着G1运作期间不会产生内存空间碎片,收集后能提供规整的可用内存。这种特性有利于程序长时间运行,分配大对象时不
10、会因为无法找到连续内存空间而提前触发下一次GC。可预测的停顿:这是G1相对于CMS的一个优势,降低停顿时间是G1和CMS共同的关注点。在G1之前的其他收集器进行收集的范围都是整个新生代或者老年代,而G1不再是这样。在堆的结构设计时,G1打破了以往将收集范围固定在新生代或老年代的模式,G1收集器将整个Java堆划分为多个大小相等的独立区域(Region)。Region是一块地址连续的内存空间,G1模块的组成如下图所示:虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。Region的大小是一致的,数值是在1M到32M字节之间的一个
11、2的幂值数,JVM会尽量划分2048个左右、同等大小的Region,这一点可以参看如下源码1。其实这个数字既可以手动调整,G1也会根据堆大小自动进行调整。#ifndef SHARE_VM_GC_G1_HEAPREGIONBOUNDS_HPP#define SHARE_VM_GC_G1_HEAPREGIONBOUNDS_HPP#include memory/allocation.hppclass HeapRegionBounds : public AllStatic private: / Minimum region size; we wont go lower than that. / We
12、might want to decrease this in the future, to deal with small / heaps a bit more efficiently. static const size_t MIN_REGION_SIZE = 1024 * 1024; / Maximum region size; we dont go higher than that. Theres a good / reason for having an upper bound. We dont want regions to get too / large, otherwise cl
13、eanups effectiveness would decrease as there / will be fewer opportunities to find totally empty regions after / marking. static const size_t MAX_REGION_SIZE = 32 * 1024 * 1024; / The automatic region size calculation will try to have around this / many regions in the heap (based on the min heap siz
14、e). static const size_t TARGET_REGION_NUMBER = 2048;public: static inline size_t min_size(); static inline size_t max_size(); static inline size_t target_number();#endif / SHARE_VM_GC_G1_HEAPREGIONBOUNDS_HPPG1收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在整个Java堆中进行全区域的垃圾收集。G1会通过一个合理的计算模型,计算出每个Region的收集成本并量化,这样一来
15、,收集器在给定了“停顿”时间限制的情况下,总是能选择一组恰当的Regions作为收集目标,让其收集开销满足这个限制条件,以此达到实时收集的目的。对于打算从CMS或者ParallelOld收集器迁移过来的应用,按照官方2的建议,如果发现符合如下特征,可以考虑更换成G1收集器以追求更佳性能:实时数据占用了超过半数的堆空间;对象分配率或“晋升”的速度变化明显;期望消除耗时较长的GC或停顿(超过0.51秒)。原文如下:Applications running today with either the CMS or the ParallelOld garbage collector would benefit switching to G1 if the application has one or more of the following traits.