引言:CMS是第一款用户线程能够和收集器线程同时工作的垃圾收集器。相较于后来的G1和ZGC垃圾收集器来说,CMS的工作流程简单,易于理解。
工作流程
- 初始标记阶段:用户线程暂停,扫描根集合,标记能够直接关联到的对象。
- 并发标记阶段:恢复用户线程,并发扫描对象图并标记对象。
- 重新标记阶段:用户线程暂停,处理并发标记阶段记录下的引用修改。
- 并发清理阶段:恢复用户线程,并发清理可回收对象。
值得一提的是CMS采用增量更新的方法来解决并发标记阶段引用的修改问题,具体的内容可以看我之前关于增量更新和原始快照的博客。
CMS的优缺点
(1)CMS的优点比较直接,CMS的响应速度快,用户体验好,因为大部分时间用户线程可以和GC线程同时工作。
(2)CMS对处理器的资源要求比较高,因为在并发阶段有部分处理器资源要被用来处理并发标记,如果处理器的核心较少时,用户线程和GC线程争抢资源,会导致用户线程的执行速度下降。而且由于用户线程和GC线程同时工作,其中对于线程切换和线程保护的资源浪费也是不可以忽略的开销。这里引用CodeCentric Blog的一篇文章来解释这种开销:
This, however, causes additional costs for thread scheduling: direct costs through context switches and indirect costs because of cache effects. Together with the costs for additional JVM-internal safety measures, this means that each GC comes along with some non-negligible overhead, which adds up with the time taken by the GC threads to perform their actual work.
用户线程和GC线程同时运行时,随着而来的是上下文切换,内存影响,安全措施等一系列除了真正工作以外的开销。
(3)CMS垃圾收集器使用的增量更新无法解决浮动垃圾问题,虽然相较于原始快照浮动垃圾略少,但还是存在。当并发阶段,浮动垃圾过多时,新对象无法分配,这时候就会启动Serial Old来整理老年代空间,暂停时间就会变长。
(4)CMS采用的是标记清除算法,内存空间碎片化,所以有可能出现大对象无法在连续空间分配的情况,这时候就要启动Serial Old垃圾收集器进行Full GC,暂停时间同样会变长。
(5)要注意的是Serial Old GC收集范围是整个堆和方法区,因而是Full GC,这里引用R大的博客原文:
“Serial Old GC的算法是mark-compact(也可以叫做mark-sweep-compact,但要注意它不是“mark-sweep”)。具体算法名是LISP2。它收集的范围是整个GC堆,包括Java heap的young generation和old generation,以及non-Java heap的permanent generation。因而它是full GC。”
总结
(1)关于GC线程额外开销的博客:https://blog.codecentric.de/en/2013/01/useful-jvm-flags-part-6-throughput-collector/
(2)CMS采用增量更新解决并发标记问题,具体内容可以看我之前关于增量更新和原始快照的博客。