魅力峰值

导航

cms和g1垃圾收集器

一、cms垃圾收集器

 

jdk1.5之后出现

在标记清理方法上做优化

1、初始标记

2、并发标记

3、重新标记

4、并发清除

在1、3会StopTheWorld

 

描述

  1. 初始标记仅仅只是标记GC Root可以直接关联到的对象,速度很快。并发标记是进行 GC Root tracting的过程,重新标记是为了修正并发标记的浮动垃圾。这个过程比初始标记稍稍慢一点点,但是比重新标记要快
  2. 由于整个过程耗时较长的并发标记和并发清理是和用户线程一起工作的,所以停顿时间就比较短

 

 

关键

关键

安全点停顿

并发过程对线程做了优化:在os4核以上,对用户影响很小,4核一下,采用增量式并发收集器,模拟多线程。实际是用户线程和gc线程交互执行。

 

优点

1、并发手机、

2、低停顿

缺点:

1、无法处理浮动垃圾,由于在并发清理过程中,由于与用户线程一起工作,此时用户产生的垃圾,无法清理。并且如果老年代设置的太高,更容易导致full gc产生。

jvm参数 -XX:CmsInitiatingOccupyingFraction

,触发垃圾回收内存百分比降低,减少fullgc的触发

2、cms是标记-清理垃圾回收算法的优化。所以容易产生更多的内存碎片。

-xx:CMSFullGCBeforeCompaction 设置进行几次不压缩的垃圾回收之后,进行内存压缩

 

二、g1垃圾收集器

jdk1.7之后出现

1、特点:

 1)并行与并发:充分利用多cpu,其他垃圾收集器需要停顿的,g1不需要停顿

2)分代收集:不需要其他垃圾收集器配合就能够执行分代收集(管理新对象和多次回收存活的对象)

3)空间整合:类似标记-整理算法(从整体看),从局部看是复制算法。

4)可预测停顿:建立可预测的时间停顿模型,停顿时间控制在M毫秒之内。

 2、化零为整思想:

 STW原因:经典的垃圾收集器都是对整个新生代或老年代进行垃圾回收,要扫描的对象太多了

 但STW又是每个垃圾收集器都不可避免的,垃圾收集器的发展就是为了能够尽量缩短STW的时间。

 G1采用了开创性的局部收集的设计思路和以Region为基本单位的内存布局方式,它将Java堆空间划分成多个大小相等的独立区域(Region),JVM目标是总共不超过2048个Region(由JVM源码参数TARGET_REGION_NUMBER定义),虽然可以超过该值,但不推荐。

 通常Region的大小等于堆空间总大小除以Region的个数,比如堆空间大小为4096MB,总共有2048个Region,那么每个Region的大小为2MB,也可以通过参数-XX:G1HeapRegionSize来指定Region的大小,假设参数值为4MB,那么堆空间就只有1024个Region了,一般推荐默认的计算方式。

 3、管理方式

 G1虽然抛弃了将新生代和老年代作为整块内存空间的方式,但依然保留了新生代和老年代的概念。只是老年代和新生代的内存空间不再是物理连续的了,它们都是Region的集合。

 G1将所有Region分为四种类型:Eden、Survivor、Old、Humongous。

 默认新生代的Region内存占堆空间的5%,如果堆空间大小为4096MB,那么新生代占用200MB左右的内存,按照每个Region为2MB,对应就是100个Region。也可以通过参数-XX:G1NewSizePercent设置新生代初始占比。

在系统运行过程中,JVM会动态地给年轻代增加更多的Region,但新生代的占比最多不会超过60%,可以通过参数-XX:G1MaxNewSizePercent设置。Region的区域类型是动态变化的,可能之前是年轻代,经过了垃圾回收之后就变成了老年代。

G1中的新生代依然与经典垃圾收集器中一样,分为Eden区和Survivor区,默认比例也是8:1:1,如果新生代有100个Region,那么就是Eden区占用80个,两个Survivor区各占用10个。 

G1收集器对于对象从新生代转移到老年代与CMS等经典垃圾收集器是一样的,但对于大对象的处理有所不同。G1为大对象的内存分配专门设计了一个Humongous类型的Region,而不再是让对象直接进入老年代的Region。

在G1中,大对象的判断是超过一个Region大小的50%,按照每个Region大小为2MB来计算,只要对象超过了1MB,就会被放入到Humongous的Region中,如果一个对象太大,一个Region放不下,可能会存在跨多个Region来存放。

在进行Full GC的时候除了要收集新生代和老年代的Region外,还会将Humongous的Region一并进行回收。

与之前的经典垃圾收集器不同,G1可以由用户自己去设置STW的最大时间,然后G1会根据STW的时间来进行内存回收,这个STW的时间包含了并发标记、最终标记和筛选回收三个阶段STW的总时间。

 4、垃圾回收过程

 G1在进行垃圾收集的时候,会根据每个Region预计垃圾收集所需时间与预计回收内存大小的占比来选择对哪些区域进行回收,也就是不再有Minor GC/Yong GC和Major GC/Full GC的概念,而是采用一种Mixed GC的方式,即混合回收的GC方式。

 

 G1的垃圾收集(主要指Mixed GC)过程可以分为下面几个步骤:

1)初始标记

需要暂定所有线程,即STW,并记录下GC Roots能直接引用的对象,速度很快。与CMS的初始标记一样

2)并发标记

可以与应用线程一起工作,进行可达性分析,与CMS的并发标记一样

3)最终标记

需要暂定所有线程(STW),根据三色标记算法修复一些引用的状态,与CMS的重新标记是一样的

4)筛选回收

筛选回收阶段会对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿STW时间(可以通过参数 -XX:MaxGCPauseMillis设置)来制定回收计划。 

比如此时有1000个Region都满了,但根据用户设置的STW时间,本次垃圾回收只能停顿200毫秒,那么通过之前的回收成本计算,200毫秒只能回收600个Region的内存空间,那么G1就会只回收这600个Region(Collection Set,要回收的集合)的内存空间,尽量把GC的停顿时间控制在用户指定的停顿时间内。 

在回收的时候,使用的是复制算法,将一个Region中的存活对象移动到另一个空的Regin中,然后将之前的Region内存空间清空,G1就不需要像CMS那样回收完内存后因为有很多脆片还要进行整理,采用复制算法几乎不会有内存碎片。

 CMS在并发清理阶段,垃圾收集线程是可以与用户线程一起并发执行,但G1因为内部实现太复杂就没有实现并行回收,不过到了ZGC就实现了并发收集。

在筛选回收阶段对各个Region进行回收价值和成本进行排序,这句话怎么理解? 

比如现在有Region1、Region2和Region3三个区域,其中Region1预计可以回收1.5MB内存,预计耗时2MS;Region2预计可以回收1MB内存,预计耗时1MS;Region3预计可以回收0.5MB内存,预计耗时1MS。那么Region1、Region2和Region3各自的回收价值与成本比值分别是:0.75、1和0.5。

比值越高说明同样的付出,收益越高,如果此时只能回收一个Region的内存空间,G1就会选择Region2进行回收。这种方式保证了G1收集器在有限的时间内尽可能地提高收集效率。

在《GC收集器》这篇文章介绍CMS底层算法的时候,遗留了一个问题,在G1的最终标记和CMS的重新标记过程中,为什么G1使用原始快照(SATB),而CMS使用增量更新?

主要原因是增量更新是一种深度扫描的算法,CMS中只有一块老年代,即使进行深度更新也没有什么问题。但G1有很多个Region,且对象的引用可能分散在多个Region中,如果这种情况还选择深度扫描效率就会很低了。而STAB相对增量更新就会快很多,虽然STAB可能产生浮动垃圾,但产生的浮动垃圾下一次再回收就好了,并且这部分浮动垃圾也不会很多。所以G1选择STAB,只是将对象简单标记成黑色,保证本次垃圾收集不进行回收就可以了,等下一次GC时再做深度更新。

5、 G1垃圾收集分类

G1的垃圾收集分为YoungGC、Mixed GC和Full GC。

1 Young GC

G1与之前垃圾收集器的Young GC有所不同,并不是当新生代的Eden区放满了就进行垃圾回收,G1会计算当前Eden区回收大概需要多久的时间,如果回收时间远小于参数-XX:MaxGCPauseMills设定的值,那么G1就会增加年轻代的Region(可以从老年代或Humongous区划分Region给新生代),继续给新对象存放;直到下一次Eden区放满,G1计算回收时间接近参数-XX:MaxGCPauseMills设定的值,那么就会触发Young GC。

2 Mixed GC

如果老年代的堆空间内存占用达到了参数-XX:InitiatingHeapOccupancyPercent设定的值就会触发Mixed GC,回收所有的新生代和部分老年代(根据用户设置的GC停顿时间来确定老年代垃圾收集的先后顺序)以及Humongous区。正常情况下G1的垃圾收集是先做Mixed GC,主要使用复制算法,需要把各个Region中存活的对象复制到另一个空闲的Region,如果在复制过程中发现没有足够的空Region放复制的对象,那么就会触发一次Full GC。

3 Full GC

停止系统程序,然后采用单线程进行标记、清理和压缩整理,好空闲出来一批Region来供下一次Mixed GC使用,这个过程是非常耗时的。

G1收集器的Region内存回收时,会涉及到大量跨区引用的对象,解决方式也是通过记忆集(卡表)。每个Region都要维护一个其他Region对自己内部对象的引用。CMS中只有新生代和老年代,即使出现了跨代引用,也很好解决。

但G1是把堆分成了多个Region,Region中对象的可能被多个Region引用,所以G1的卡表实现要比CMS复杂很多。

6、优化

假设参数-XX:MaxGCPauseMills设置的值很大,导致系统运行很久,年轻代可能都占用了堆内存的60%了,此时才触发年轻代gc。

那么存活下来的对象可能就会很多,此时就会导致Survivor区域放不下那么多的对象,就会进入老年代中。

或者是年轻代GC过后,存活下来的对象过多,导致进入Survivor区域后触发了动态年龄判定规则,达到了Survivor区域的50%,也会快速导致一些对象进入老年代中。 

所以这里核心还是在于调节-XX:MaxGCPauseMills这个参数的值,在保证他的年轻代GC别太频繁的同时,还得考虑每次GC过后的存活对象有多少,避免存活对象太多快速进入老年代,频繁触发Mixed GC。

 

什么场景适合使用G1?

50%以上的堆被存活对象占用

对象分配和晋升的速度变化非常大

垃圾回收时间特别长,超过1秒

8GB以上的堆内存(建议值)

停顿时间是500ms以内

posted on 2023-07-20 14:02  魅力峰值  阅读(59)  评论(0编辑  收藏  举报