上篇文章说了CMS垃圾收集器是赋值清除,所以他不可以碎片整理,于是jvm支持两个参数,几次fullGC之后碎片整理压缩空间。Cms他会抢占cpu资源,因为是并行运行,所以会有浮动垃圾。还有执行不确定性,垃圾收集完,继续进入新的对象,导致异常concurrent mode faliture,最后用serial old处理,可以用jvm的fraction参数来参数百分之多少的时候需要GC,这样就预留充足的空间存储新对象。
垃圾收集器CMS-JVM(十一)
一、实际场景
前面介绍了cms的参数,那么我们如何应用呢?
前面文章我们介绍了parallel作用于年轻代和老年代回收,当时场景是因为回收的对象比较大,不能进入survivor,导致直接从eden进入old,这时候eden满的那一瞬间,也就是minor GC时候最后一个对象不会在年轻代回收,进入老年代,当时我们通过设置年轻代大小的参数,来解决了减少fullGC的问题。
那我们现在既然学习了cms和parNew,就试着用新的方法来解决。
前面我们的参数jvm参数配置是:
Java -Xms3072M -Xmx3072M -Xmn2048M -Xss1M -XX:MetaspaceSize=256M
-XX:MaxMetaspaceSize=256M -XX SurvivorRatio=8
这样配合当时的应用场景绝对不发生fullGc是不可能的。当时我们的场景是每秒产生60MB,但如果峰值增加,在23秒,24秒的时候,每次cpu分配给单个线程的运行已经超过几秒,这时候minor GC的时候会超过60M的数据移动到survivor,这时候200M的S0已经未必放的下这些存活的数据,需要移动到old,这时候当old满的时候又会触发fullGC。
但这种秒杀场景,即使出现这种情况影响也不大,因为当old满的时候,秒杀的前10分钟已经过去,这时候发生一次fullGC也不影响。
Java -Xms3072M -Xmx3072M -Xmn2048M -Xss1M -XX:MetaspaceSize=256M
-XX:MaxMetaspaceSize=256M -XX SurvivorRatio=8
-XX:MaxTenuringThreshold=5 -XX:PretenureSizeThreshold=1M
-XX:UseParNewGC -XX: UseConcMarkSweepGC
-XX:UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=3
这个参数一次的fullGC不会存在大量参数,所以一次未必需要整理,配置两到三次都没有问题。
二、三色标记
这个底层并不是java实现,而是c 实现的。
前面说了在gc发生的时候,用户线程和垃圾回收线程并行运行,对于多标和漏标的情况可能会发生。
多标影响还好,可以在下一次gc的时候清除,在并发标记的时候,就会存在多标的现象,但是少标或者漏标影响比较大。
三色标记指GCroots 可达性分析遍历对象过程中遇到的对象,按照是否访问过标记为三种颜色。
黑色:表示对象已经被垃圾收集器访问过,且这个对象所有引用都扫描过,它是存活的对象。如果其他对象引用指向黑色,无须重新扫描。黑色对象不可以直接(不经过灰色对象)指向某个白色对象。
灰色:表示对象已经被垃圾回收访问过,但至少还有一个或者以上引用没被扫描到。
白色:表示对象未被垃圾回收器访问过。
前面说的多标浮动垃圾对象,三色标记的处理办法则是直接标记成黑色,本轮GC不会清除,但是下一轮则可能部分对象变为垃圾对象。
漏标-读写屏障
漏标则会把未标记的对象无删除,这种验证bug则会有两种办法解决,增量更新和原始快照。
漏标会找到之前的引用,重新去扫描。
增量更新指一旦有新插入的指向白色,则会变成灰色对象。
原始快照则是让对象在本轮gc存活,在下一次gc再清理,可以理解为浮动垃圾。
这些都是在写屏障实现的。
三、记忆集与卡表
新生代做gcRoots 的时候可能遇到跨代应用场景,这时候总不能再去old代理扫描一遍。
于是新生代里有一个记录集(remember set)数据结构,记录这种跨代应用的对象,避免GC roots去老年代扫描。事实上,老年代也有这种问题,G1和ZGC收集器都有这种问题。
hotSpot使用叫做卡表Cardtable的方式实现这种收集,也是目前最常用的一种方式。