[TOC]
1、CMS
基本原理
简单来说,为了避免长时间“Stop the World”,CMS采用了4个阶段来垃圾回收,其中初始标记和重新标记,耗时很短,虽然会导致“Stop the World”,但是影响不大。 然后并发标记和并发清理,两个阶段耗时最长,但是是可以跟系统的工作线程并发运行的,所以对系统没太大影响。
初始标记
虽然说要造成“Stop the World”暂停一切工作线程,但是其实影响不大,因为他的速度很快,仅仅标记类的静态变量代表的GC Roots直接引用的那些对象罢了。
一个参数是“-XX:+CMSParallelInitialMarkEnabled”,这个参数会在CMS垃圾回收器的“初始标记”阶段开启多线程并发执行。大家应该还记得初始标记阶段,是会进行Stop the World的,会导致系统停顿,所以这个阶段开启多线程并发之后,可以尽可能优化这个阶段的性能,减少Stop the World的时间。另外一个参数是“-XX:+CMSScavengeBeforeRemark”,这个参数会在CMS的重新标记阶段之前,先尽量执行一次Young GC。
并发标记
这个阶段会让系统线程可以随意创建各种新对象,继续运行在运行期间可能会创建新的存活对象,也可能会让部分存活对象失去引用,变成垃圾对象。在这个过程中,垃圾回收线程,会尽可能的对已有的对象进行GC Roots追踪。所谓进行GC Roots追踪,就是遍历整个对象图的过程, 这个阶段很耗时,但因为是可以跟系统程序并发运行的,所以这个阶段不会对系统造成影响。
重新标记
因为第二阶段里,你一边标记存活对象和垃圾对象,一边系统在不停运行创建新对象,让老对象变成垃圾所以第二阶段结束之后,绝对会有很多存活对象和垃圾对象,是之前第二阶段没标记出来的,
所以此时进入第三阶段,要继续让系统程序停下来,再次进入“Stop the World”阶段。然后重新标记下在第二阶段里新创建的一些对象,还有一些已有对象可能失去引用变成垃圾的情况
并发清理
这个阶段就是让系统程序随意运行,然后他来清理掉之前标记为垃圾的对象即可。这个阶段其实是很耗时的,因为需要进行对象的清理,但是他也是跟系统程序并发运行的,所以其实也不影响系统程序的执行
并发回收垃圾导致CPU资源紧张
在并发标记和并发清理两个最耗时 的阶段,垃圾回收线程和系统工作线程同时工作,会导致有限的CPU资源被垃圾回收线程占用了一部分
是因为老年代里存活对象是比较多的,这个过程会追踪大量的对象,所以耗时较高。并发清理,又需要把垃圾对象从各种随机的内存位置清理掉,也是比较耗时的。 所以在这两个阶段,CMS的垃圾回收线程是比较耗费CPU资源的。CMS默认启动的垃圾回收线程的数量是(CPU核数 + 3)/ 4。用最普通的2核4G机器和4核8G机器来计算一下,假设是2核CPU,本来CPU资源就有限,结果此时CMS还会有个“(2 + 3) / 4”= 1个垃圾回收线程,去占用宝贵的一个CPU。
所以其实CMS这个并发垃圾回收的机制,第一个问题就是会消耗CPU资源。
Concurrent Mode Failure问题
在并发清理阶段,CMS只不过是回收之前标记好的垃圾对象但是这个阶段系统一直在运行,可能会随着系统运行让一些对象进入老年代,同时还变成垃圾对象,这种垃圾对象是“浮动垃圾”。
因为他虽然成为了垃圾,但是CMS只能回收之前标记出来的垃圾对象,不会回收他们,需要等到下一次GC的时候才会回收他们。所以为了保证在CMS垃圾回收期间,还有一定的内存空间让一些对象可以进入老年代,一般会预留一些空间。
CMS垃圾回收的触发时机,其中有一个就是当老年代内存占用达到一定比例了,就自动执行GC。
“-XX:CMSInitiatingOccupancyFaction”参数可以用来设置老年代占用多少比例的时候触发CMS垃圾回收,JDK 1.6里面默认的值是92%。也就是说,老年代占用了92%空间了,就自动进行CMS垃圾回收,预留8%的空间给并发回收期间,系统程序把一些新对象放入老年代中。
那么如果CMS垃圾回收期间,系统程序要放入老年代的对象大于了可用内存空间,此时会如何? 这个时候,会发生Concurrent Mode Failure,就是说并发垃圾回收失败了,我一边回收,你一边把对象放入老年代,内存都不够了。此时就会自动用“Serial Old”垃圾回收器替代CMS,就是直接强行把系统程序“Stop the World”,重新进行长时间的GC Roots追踪,标记出来全部垃圾对象,不允许新的对象产生 然后一次性把垃圾对象都回收掉,完事儿了再恢复系统线程,这就很悲催了。
内存碎片问题
是老年代的CMS采用“标记-清理”算法,每次都是标记出来垃圾对象,然后一次性回收掉,这样 会导致大量的内存碎片产生。如果内存碎片太多,会导致后续对象进入老年代找不到可用的连续内存空间了,然后触发Full GC。所以CMS不是完全就仅仅用“标记-清理”算法的,因为太多的内存碎片实际上会导致更加频繁的Full GC。CMS有一个参数是“-XX:+UseCMSCompactAtFullCollection”,默认就打开了 他意思是在Full GC之后要再次进行“Stop the World”,停止工作线程,然后进行碎片整理,就是把存活对象挪到一起,空出来大片连续内存空间,避免内存碎片。
还有一个参数是“-XX:CMSFullGCsBeforeCompaction”,这个意思是执行多少次Full GC之后再执行一次内存碎片整理的工作,默认是0,意思就是每次Full GC之后都会进行一次内存整理。
CMS 调优参数
1、-XX:+CMSParallelInitialMarkEnabled表示在初始标记的多线程执行,减少STW;
2、-XX:+CMSScavengeBeforeRemark:在重新标记之前执行minorGC减少重新标记时间;
3、-XX:+CMSParallelRemarkEnabled:在重新标记的时候多线程执行,降低STW;
4、-XX:CMSInitiatingOccupancyFraction=92和-XX:+UseCMSInitiatingOccupancyOnly配套使用,如果不设置后者,jvm第一次会采用92%但是后续jvm会根据运行时采集的数据来进行GC周期,如果设置后者则jvm每次都会在92%的时候进行gc;
-XX:+PrintHeapAtGC:在每次GC前都要GC堆的概况输出