CMS 垃圾回收的基本原理
一般来说,老年代我们选择的垃圾回收器是 CMS,他采用的是 标记-清理 算法,其实就是,先通过追踪 GC Roots 的方法,看各个对象是否被 GC Roots 给引用了,如果是,那就是存活对象,否则就是垃圾对象。
这种方法最大的问题是,会造成很多内存碎片。
如果 Stop the World 然后垃圾回收会如何?
CMS垃圾回收器采取的是垃圾回收线程和线程系统工作线程尽量同时执行的模式来处理。
CMS 如何实现系统一边工作一边进行垃圾回收
CMS 在执行一次垃圾回收的过程一共分为4个阶段:
初始标记
并发标记
重新标记
并发清理
初始标记:会造成 “stop the world”
所谓初始标记,就是标记出所以 GC Roots 直接引用的对象。这是什么意思呢?
public class Kafka{ private static ReplicaManager replicaManager = new ReplicaManager(); } public class ReplicaManager { private ReplicaFetcher replicaFetcher = new ReplicaFetcher(); }
在初始标记阶段,仅仅会通过,“replicaManager” 这个类的静态变量代表 GC Roots ,去标记出来他的直接引用 ReplicaManager 对象,这就是初始标记过程。
而不会去管 ReplicaFetcher 这种对象,因为 ReplicaFetcher 对象是被 ReplicaManager 类的 replicaFetcher 实例变量引用的。
之前说过,方法的局部变量 和 类的静态变量是 GC Roots ,但是 类的实例变量不是。
虽然,初始标记会造成 “stop the world”暂停一切工作线程,但是影响不大,因为他速度很快,仅仅标记 GCRoots 直接引用的对象。
并发标记:这个阶段不会造成“stop the world”,系统线程可以随意创造各种对象。
在这个过程中会对所有对象进行 GC Roots 追踪。意思就是对类似 “ReplicaFetcher” 之类的全部老年代的对象,会看被谁引用来?
比如上面说的,ReplicaFetcher 被 “ReplicaManager” 对象实例引用来,接着会看, “ReplicaManager” 对象被谁引用?会发现被 “Kafka” 类的静态变量引用类。
那么此时,就可以认定 “ReplicaFetcher” 对象是被 GC Roots 间接引用的,所以此时不需要回收他。
这个阶段,会对老年代所有对象进行 GC Roots 追踪,其实是最耗时的,但是这个阶段,是跟系统程序并发运行的,所以这个阶段不会对系统运行造成影响。
重新标记:再次进入“stop the world”阶段
因为在并发标记阶段,一边标记存活对象 和 垃圾对象,一边系统不停创建对象,让老对象变成垃圾,所以第二阶段结束后,必定有很对存活对象变成垃圾对象,是之前在第二阶段没有标记出来的。
在这个重新标记阶段会速度很快,因为他其实是对 第二阶段中被系统程序运行变动过的少数对象进行标记,所以很快。
并发清理: 不会导致“stop the world”
这个阶段,其实是很耗时的,因为需要进行对象清理,但是他也是跟系统并发运行的,所以其实也不影响系统程序的执行。