老年代回收容器——CMS
1、CMS工作原理
一般老年代我们选择的垃圾回收器是CMS,他采用的是
标记清理算法
(不是标记整理)。之前提到过
Stop the World
状态,就是垃圾回收时停止一切线程的工作,如果在这个状态下再去慢慢执行标记清理算法,会导致系统卡死时间过长,所以CMS垃圾回收器采取的是
垃圾回收线程和系统工作线程尽量同时执行的模式
来处理的。
工作原理
:为了避免长时间
Stop the World
,CMS采用了4个阶段来垃圾回收,分别是初始标记、并发标记、重新标记和并发清理。其中初始标记和重新标记,耗时很短,虽然会导致
Stop the World
,但是影响不大,然后并发标记和并发清理,两个阶段耗时最长,但是是可以跟系统的工作线程并发运行的,所以对系统没太大影响。
2、CMS垃圾回收的四个阶段
(1)初始标记——标记直接GC Roots(直接)
在这个阶段让系统的工作线程全部停止,进入
Stop the World
状态。同时
标记所有GC Roots直接引用的对象
,是直接引用!比如下面这段代码,仅仅会通过
replicaManager
这个类的静态变量代表的GC Roots,去标记出来他直接引用的
ReplicaManager
对象,不会去管
ReplicaFetcher
这种对象,因为
ReplicaFetcher
对象是被
ReplicaManager
类的
replicaFetcher
实例变量引用的。(之前说过,方法的局部变量和类的静态变量是GC Roots。但是类的实例变量不是GC Roots。)
public class Kafka {
private static ReplicaManager replicaManager = new ReplicaManager();
}
public class ReplicaManager {
private ReplicaFetcher replicaFetcher = new ReplicaFetcher();
}
初识标记如图所示
(2)并发标记——对老年代所有对象进行GC Roots追踪(最耗时)
这个阶段会让系统线程可以随意创建各种新对象,继续运行。在运行期间可能会创建新的存活对象,也可能会让部分存活对象失去引用,变成垃圾对象。在这个过程中,垃圾回收线程,会尽可能的对已有的对象进行GC Roots追踪。GC Roots追踪,意思就是对类似
ReplicaFetcher
之类的全部老年代里的对象,他会去看他被谁引用了,
认定为是被GC Roots间接引用后,就不需要回收它。因为老年代里存活对象是比较多的,这个过程会追踪大量的对象,所以耗时较高。
(3)重新标记
第二阶段里,你一边标记存活对象和垃圾对象,一边系统在不停运行创建新对象,让老对象变成垃圾,所以第二阶段结束之后,绝对会有很多存活对象和垃圾对象,是之前第二阶段没标记出来的。在这个阶段,要再次进入
Stop the World
阶段,重新标记下在第二阶段里新创建的一些对象,还有一些已有对象可能失去引用变成垃圾的情况。重新标记的阶段只是对变动过的
少数对象
进行标记,是速度很快的
(4)并发清理
这个阶段就是让系统程序随意运行,然后清理掉之前标记为垃圾的对象即可,也是很耗时的。
3、CMS性能分析
(1)好的方面
CMS的第二阶段和第四阶段,都是很耗时的,但都和系统程序是并发执行的,所以基本这两个最耗时的阶段对性能影响不大。只有第一个阶段和第三个阶段是需要
Stop the World
的,但是这两个阶段都是简单的标记而已,速度非常的快,所以基本上对系统运行响应也不大。
(2)坏的方面
①并发回收导致CPU资源紧张。
并发标记和并发清理两个最耗时的阶段,使垃圾回收线程和系统工作线程同时工作,导致有限的CPU资源被垃圾回收线程占用了一部分。在这两个阶段,CMS的垃圾回收线程是比较耗费CPU资源的。CMS默认启动的垃圾回收线程的数量是(CPU核数 + 3)/ 4,比如的2核4G机器,就会占用(2+3)/4 = 1个CPU被用来垃圾回收。
②Concurrent Mode Failure问题
在并发清理阶段,CMS只不过是回收之前标记好的垃圾对象,但这个时候系统一直在运行,先把某些对象分配在新生代,然后可能触发了一次Minor GC,一些对象进入了老年代,在短时间内又没人使用这些对象,这种垃圾对象就是
浮动垃圾
,虽然它是垃圾,但是不会回收他们,要等到下一次才能回收。
CMS垃圾触发的时机是当老年代内存占用到达一定比例时,就会自动GC
,
-XX:CMSInitiatingOccupancyFaction
这个参数可以设置老年代内存占用到多少比例时触发垃圾回收。JDK 1.6默认是92%。预留8%的空间给并发回收期间,系统程序把一些新对象放入老年代中。如果垃圾回收期间,要放入的对象大于可用内存空间,就会发生
Concurrent Mode Failure
,即并发垃圾回收失败了,我一边回收,你一边把对象放入老年代,内存都不够了。此时就会自动用
Serial Old
垃圾回收器替代CMS,就是直接强行把系统程序
Stop the World
,重新进行长时间的GC Roots追踪,标记出来全部垃圾对象,不允许新的对象产生。
③内存碎片问题
老年代的CMS采用
标记清理
算法(不是
标记整理
),每次都是标记出来垃圾对象,然后一次性回收掉,这样会导致大量的内存碎片产生,太多的内存碎片实际上会导致更加频繁的Full GC。
CMS有一个参数是
-XX:+UseCMSCompactAtFullCollection
,默认是打开的,意思是在Full GC之后要再次进行
Stop the World
,停止工作线程,然后进行碎片整理,就是把存活对象挪到一起,空出来大片连续内存空间,避免内存碎片。
还有一个参数是
-XX:CMSFullGCsBeforeCompaction
,这个意思是执行多少次Full GC之后再执行一次内存碎片整理的工作,默认是0,意思就是每次Full GC之后都会进行一次内存整理,存活对象都放在一起,然后空出来大片连续内存空间可供使用。
红圈处即浮动垃圾