菜鸟笔记
提升您的技术认知

JVM: G1和CMS的区别

CMS:以获取最短回收停顿时间为目标的收集器,基于并发“标记清理”实现

有人会好奇为什么标记清理算法会产生内存碎片!但是CMS仍采用这种算法呢?

答案是:因为CMS作为第一款实现用户线程和收集线程并发执行的收集器!当时的设计理念是减少停顿时间,最好是能并发执行!但是问题来了,如要用户线程也在执行,那么就不能轻易的改变堆中对象的内存地址!不然会导致用户线程无法定位引用对象,从而无法正常运行!而标记整理算法和复制算法都会移动存活的对象,这就与上面的策略不符!因此CMS采用的是标记清理算法!

** 初始标记-->并发标记---->重新标记---->并发清理**

过程:

1、初始标记:独占PUC,stop-the-world, 仅标记GCroots能直接关联的对象

2、并发标记:可以和用户线程并发执行,通过GCRoots Tracing 标记所有可达对象

3、重新标记:独占CPU,stop-the-world, 对并发标记阶段****用户线程运行产生的垃圾对象进行标记修正,以及更新自我拯救那部分逃逸对象

4、并发清理:可以和用户线程并发执行,清理垃圾

优点:

并发,低停顿

缺点:

1、对CPU非常敏感:在并发阶段虽然不会导致用户线程停顿,但是会因为占用了一部分线程使应用程序变慢

2、无法处理浮动垃圾:在最后一步并发清理过程中,用户线程执行也会产生垃圾,但是这部分垃圾是在标记之后,所以只有等**到下一次gc的时候清理掉,这部分垃圾叫浮动垃圾。由于并发清理的时候,用户线程也在运行,就需要保证用户线程在运行的时候需要留有部分内存以供使用。 但是当这部分内存不足以给用户线程正常使用时,就会出现一次 “Concurrent Mode Failure**,一旦出现了“Concurrent Mode Failure”,便会开启后备方案,临时使用SerialOld收集器进行收集工作。

3、CMS使用“标记-清理”法会产生大量的空间碎片,当碎片过多,将会给大对象空间的分配带来很大的麻烦,往往会出现老年代还有很大的空间但无法找到足够大的连续空间来分配当前对象,不得不提前触发一次FullGC

为了解决这个问题CMS提供了一个开关参数,用于在CMS顶不住,要进行FullGC时开启内存碎片的合并整理过程,但是内存整理的过程是无法并发的,空间碎片没有了但是停顿时间变长了

CMS 出现FullGC的原因:

1、年轻代晋升到老年代没有足够的连续空间,很有可能是内存碎片导致的,因此会触发FULL GC

2、在并发过程中jvm觉得在并发过程结束之前堆就会满,需要提前触发FullGC

CMS失败后使用备案SerialOld收集器

G1:是一款面向服务端应用的垃圾收集器

初始标记-->并发标记---->最终标记---->筛选回收

** G1只有并发标记阶段能做到用户线程和回收线程并发执行!!!!**

G1可以不需要其它收集器配合就能独立管理整个GC堆

目标是替换掉CMS收集器

特点:

1、并行与并发:G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行。

2、分代收集:分代概念在G1中依然得以保留。**虽然G1可以不需要其它收集器配合就能独立管理整个GC堆,但它能够采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。**也就是说G1可以自己管理新生代和老年代了。

3、空间整合,没有内存碎片产生:由于G1使用了独立区域(Region)概念,G1从整体来看是基于“标记-整理”算法实现收集,从局部(两个Region)上来看是基于“复制”算法实现的,但无论如何,这两种算法都意味着G1运作期间不会产生内存空间碎片。

在最后筛选回收阶段,对每个region里的回收对象价值(回收该区域的时间消耗和能得到的内存比值)最后进行排序,用户可以自定义停顿时间,那么G1就可以对部分的region进行回收!这使得停顿时间是用户自己可以控制的!!!!

但是每个region之间是有互相引用的依赖关系的!这导致在MinorGC的时候会同时对老年代进行扫描(甚至是整个堆扫描),那就会导致MinorGC的效率低下,时间变长!

如何解决???

维护一个Remebered Set集合来存放各个Region之间的引用关系!当进行GC Roots Tracing 的时候就可以只扫描set里的关联region!而不用全堆扫描啦!!!

4、可预测的停顿:这是G1相对于CMS的另一大优势,降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用这明确指定一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。

可预测的停顿什么意思呢?

G1可以有计划的避免在整个JAV堆中进行垃圾收集,可以对每个region里的回收对象价值(回收该区域的时间消耗和能得到的内存比值)进行分析,在最后筛选回收阶段,对每个region里的回收对象价值(回收该区域的时间消耗和能得到的内存比值)最后进行排序,用户可以自定义停顿时间,那么G1就可以对部分的region进行回收!这使得停顿时间是用户自己可以控制的!!!!

与其它收集器相比,G1变化较大的是它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留了新生代和来年代的概念,但新生代和老年代不再是物理隔离的了它们都是一部分Region(不需要连续)的集合。同时,为了避免全堆扫描,G1使用了Remembered Set来管理相关的对象引用信息。当进行内存回收时 ,在GC根节点的枚举范围中加入Remembered Set即可保证不对全堆扫描也不会有遗漏了。

最后筛选回收阶段首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划(可预测的停顿,这一过程同样是需要停顿线程的,但Sun公司透露这个阶段其实也可以做到并发,但考虑到停顿线程将大幅度提高收集效率,所以选择停顿。下图为G1收集器运行示意图: