cms回收为什么要停顿两次?
cms回收为什么要停顿两次?
答案:以最少的STW成本,找出要清理的垃圾。
什么是STW
暂停用户线程 - Stop The World
为什么要STW
如果不暂停用户线程,就意味着不断有垃圾的产生,永远也清理不干净;
其次,因为清理垃圾用的标记清除算法,用户线程的运行必然会导致对象的引用关系发生变化,即标记的变化,,这样就会导致两种情况:漏标和错标。
- 漏标:原来不是垃圾,但是在GC的过程中,用户线程将其引用关系修改,变成了null引用,成为了垃圾,这种情况还好,无非就是产生了一些浮动垃圾,下次GC再清理就好了;
- 错标:与漏标对应的就是错标,一个对象,开始没有引用,但是GC的同时,用户线程又重新引用了它,但是这个时候,我们把它当作垃圾清理掉了,这将会导致程序运行错误。
三色标记算法
前边讲了两点,什么是暂停用户线程和为什么要暂停用户线程,现在接着讲cms是怎么样来识别垃圾对象的。
垃圾对象:简单的说,就是判断是否有引用,如果某个对象,已经没有任何引用指向它,就把该对象定义为垃圾对象,即我们要清理的对象,这个的核心就是可达性分析算法。
标记步骤:
- 开所有的对象都是白色
- 直接关联的对象设置为灰色
- 遍历灰色对象的所有引用,灰色对象本身置为黑色,引用置为灰色
- 重复步骤3,直到没有灰色对象为止
- 结束时,黑色对象存活,白色对象回收
这个过程正确执行的前提是没有其他线程改变对象间的引用关系。
cms清理步骤
- 初试标记
初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快。初始标记的过程是需要触发STW的,不过这个过程非常快,而且初试标记的耗时不会因为堆空间的变大而变慢,是可控的,因此可以忽略这个过程导致的短暂停顿。
- 并发标记
并发标记就是将初始标记的对象进行深度遍历,以这些对象为根,遍历整个对象图,这个过程耗时较长,而且标记的时间会随着堆空间的变大而变长。不过好在这个过程是不会触发STW的,用户线程仍然可以工作,程序依然可以响应,只是程序的性能会受到一点影响。因为GC线程会占用一定的CPU和系统资源,对处理器比较敏感。CMS默认开启的GC线程数是:(CPU核心数+3)/4,当CPU核心数超过4个时,GC线程会占用不到25%的CPU资源,如果CPU数不足4个,GC线程对程序的影响就会非常大,导致程序的性能大幅降低。
- 重新标记
由于并发标记时,用户线程仍在运行,这意味着并发标记期间,用户线程有可能改变了对象间的引用关系,可能会发生两种情况:一种是原本不能被回收的对象,现在可以被回收了,另一种是原本可以被回收的对象,现在不能被回收了。针对这两种情况,CMS需要暂停用户线程,进行一次重新标记。
- 并发清理
重新标记完成后,就可以并发清理了。这个过程耗时也比较长,且清理的开销会随着堆空间的变大而变大。不过好在这个过程也是不需要STW的,用户线程依然可以正常运行,程序不会卡顿,不过和并发标记一样,清理时GC线程依然要占用一定的CPU和系统资源,会导致程序的性能降低。
cms为什么要停顿两次?
以最少的STW成本,找出要清理的垃圾。
这里我们可以抽象的理解为全量垃圾和增量垃圾的两个概念。
清理的第一步,就是为了找出产生全量垃圾根对象,并打上标记为初始标记(耗时短,STW),同时把用户访问线程打开,并让后台线程去执行第二步并发标记,这些其实就是找出我们全量垃圾。
然后找出在我们执行并发标记这段时间由用户线程产生的增量垃圾进行重新标记(耗时短,STW),这个时候的GC标记,就是截止到当前时间,完整的垃圾信息,再执行并发清理。
推荐阅读
我是连边,专注于Java和架构领域,坚持撰写有原理,有实战,有体系的技术文章。
关注 订阅号@连边
不错过精彩文章