jvm优化之 CMS vs G1垃圾回收器
1.【CMS垃圾回收器】
(1)CMS垃圾回收器(CMS, Concurrent Mark Sweep)并行标记清理垃圾回收器,是一种多并发低停顿的垃圾收集器,回收老年代内存。垃圾收集线程与工作线程并发执行,不对内存复制或也不压缩存活的对象。如果产生内存碎片问题,会通过FULL GC 方式进行垃圾回收。
(2)缺陷:随着服务存活的时间越长,JVM中CMS的堆碎片化问题就越严重;增加老年代容量只能延迟整体的清理时间,但是并不是杜绝碎片化问题和promotion failed;
(3)CMS碎片化:CMS不是采用复制算法的垃圾收集器,这就意味着在扫描和清理阶段,只能在它的free-list中更新对象,但是不会压缩存活的对象,这就会在内存中产生一个一个的“洞”(碎片)
(4)增大内存(当前的Xmx为4GB,我们尝试将其调大到5GB,甚至10GB)并不能解决办法,它只是拖延了长时间停顿的问题,并且我们看到了更长的GC停顿。
2.【G1垃圾回收器】
(1)G1垃圾回收器是主要针对多处理器以及大内存的机器,以极高的概率满足预测GC停顿时间要求的同时,还具备高吞吐量性能特征。是基于标记整理的垃圾回收器。
(2)通过从CMS切换到G1,并且大大减少了JVM碎片化问题(G1也不能完全避免),并且几乎没有观察到延迟毛刺。
(3)这次调优已经证明,G1很大的缓解了性能问题,最大化吞吐量的同时,也最小化了延迟。
3.【G1相比CMS有更清晰的优势】
G1相比CMS有更清晰的优势:
-
CMS没有采用复制算法,所以它不能压缩,最终导致内存碎片化问题。而G1采用了复制算法,它通过把对象从若干个Region拷贝到新的Region过程中,执行了压缩处理。
-
在G1中,堆是由Region组成的,因此碎片化问题比CMS肯定要少的多。而且,当碎片化出现的时候,它只影响特定的Region,而不是影响整个堆中的老年代。
-
而且CMS必须扫描整个堆来确认存活对象,所以,长时间停顿是非常常见的。而G1的停顿时间取决于收集的Region集合数量,而不是整个堆的大小,所以相比起CMS,长时间停顿要少很多,可控很多。
【参考文献】:1. LinkedIn 从CMS到G1的调优实战,实现毫秒级响应
2. Java两种垃圾回收器G1与CMS
【CMS两次标记执行STW原因】:
(1)垃圾回收首先是要经过标记的
(2)垃圾回收并不会阻塞我们程序的线程,他是与当前程序并发执行的。
(3)当GC线程标记好了一个对象的时候,此时我们程序的线程又将该对象重新加入了“关系网”中,当执行二次标记的时候,该对象也没有重写finalize()方法,因此回收的时候就会回收这个不该回收的对象。
(4)虚拟机的解决方法就是在一些特定指令位置设置一些“安全点”,当程序运行到这些“安全点”的时候就会暂停所有当前运行的线程(Stop The World 所以叫STW),暂停后再找到“GC Roots”进行关系的组建,进而执行标记和清除。
这些特定的指令位置主要在:
1、循环的末尾
2、方法临返回前 / 调用方法的call指令后
3、可能抛异常的位置
【参考文献】:CMS垃圾回收机制
【CMS的JVM调优】:参考文献: CMS垃圾回收器详解
(1)JVM参数发现-XX:CMSScheduleRemarkEdenPenetration
的意思是当新生代存活对象占EdenSpace的比例超过多少时,终止preclean阶段并进入remark阶段。这个参数的默认值是50%
(2)希望的是在preclean阶段产生一次MinorGC,所以将preclean的最大时长调整为30秒:-XX:CMSMaxAbortablePrecleanTime=30000
。
(3)出现FULLGC,考虑如果能够缩短CMSGC的周期,保证在出现promotion failed之前就进行CMSGC,就可以避免这个问题了。所以考虑将新生代空间缩小(相对来说就增加了老生代的空间),并且将CMSGC触发比率降低,同时保证Survivor空间不变。所以优化参数改动如下
-Xmn7800m -> -Xmn7020m
-XX:SurvivorRatio=8 –> -XX:SurvivorRatio=7
-XX:CMSInitiatingOccupancyFraction=80 -> -XX:CMSInitiatingOccupancyFraction=70
(4)持久带满造成fullGC
加大持久带,并让持久带也使用CMSGC方式回收
-XX:PermSize=64m -> -XX:PermSize=200m
-XX:MaxPermSize=128m -> -XX:MaxPermSize=200m
-XX:+CMSClassUnloadingEnabled