1.CMS垃圾回收器
一般我们老年代垃圾回收使用的CMS垃圾回收器,采用的是“标记-清除”的回收算法
正常情况一下,我们会开启空间担保机制。如果老年代内存<年轻代占用的内存,会判断老年代可用是否小于历代进入老年代的平均内存大小,如果小于,则进行full gc 如果大于就正常执行minor gc. 如果minor gc之后,存活的对象大小>s区也比老年代内存大,则进行full gc.
标记清除会导致内存碎片的产生。
Cms采用垃圾回收线程和系统工作线程同时执行。
1.处理过程
1.初始标记
这个阶段会停止系统的工作线程,进行 “stop the world”
虽然说要造成“Stop the World”暂停一切工作线程,但是其实影响不大,因为他的速度很快,仅仅标
记GC Roots直接引用的那些对象罢了。
2.并发标记
这个阶段会让系统创建对象,继续执行。再执行过程,可能创建存活的对象,也有可能某些存活的对象,没人引用了,变成垃圾对象。
垃圾回收线程会尽可能的对已有对象进行Gc root
3.重新标记
再第二阶段时候一边标记存活对象和垃圾对象,但是系统线程又不断的创建新的对象,老对象变成垃圾对象
所以此时进入第三阶段,要继续让系统程序停下来,再次进入“Stop the World”阶段。
这个重新标记的阶段,是速度很快的,他其实就是对在第二阶段中被系统程序运行变动过的少数对象进行标记,所以运行速度很快。
接着重新恢复系统程序的运行,
4.并发清除
这个阶段就是让系统程序随意运行,然后他来清理掉之前标记为垃圾的对象即可。
这个阶段其实是很耗时的,因为需要进行对象的清理,但是他也是跟系统程序并发运行的,所以其实也不影响系统程序的执行,如下
图。
其实大家看完CMS的垃圾回收机制之后,就会发现,他已经尽可能的进行了性能优化了
因为最耗时的,其实就是对老年代全部对相关进行GC Roots追踪,标记出来到底哪些可以回收,然后就是对各种垃圾对象从内存里清
理掉,这是最耗时的。
但是他的第二阶段和第四阶段,都是和系统程序并发执行的,所以基本这两个最耗时的阶段对性能影响不大。
只有 第一个阶段和第三个阶段是需要“Stop the World”的,但是这两个阶段都是简单的标记而已,速度非常的快,所以基本上对系
统运行响应也不大。
CMS垃圾回收器有一个最大的问题,虽然能在垃圾回收的同时让系统同时工作,但是大家发现没有,在并发标记和并发清理两个最耗时
的阶段,垃圾回收线程和系统工作线程同时工作,会导致有限的CPU资源被垃圾回收线程占用了一部分。
为老年代里存活对象是比较多的,这个过程会追踪大量的对象,所以耗时较高。并发清理,又需要把垃圾对象从各种随机的内存
位置清理掉,也是比较耗时的。
Concurren Mode failure的问题:因为再垃圾回收的过程中,系统也再运行,可能一些对象进入老年代同时又变成垃圾对象,这样的称为浮动垃圾。
大家看上图那个红圈画的地方,那个对象就是在并发清理期间,系统程序可能先把某些对象分配在新生代,然后可能触发了一次Minor
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垃圾回收的比例需要合理优化一下,避免“Concurrent Mode Failure”问题