jvm原理(46)CMS垃圾收集器深入讲解
CMS收集器
CMS (concurrent mark sweep)收集器,以获取最短回收停顿时间为目标,多数应用于互联网站或者b/s系统的服务器上。
cms是基于“标记-清除”算法实现的,整个过程分为4个步骤:
- 初始标记(cms initial mark)
- 并发标记(cms concurrent mark)
- 重新标记(cms remark)
- 并发清除(cms concurrent sweep)
其中,初始标记、重新标记这两个步骤需要stw(stop the world)
初始标记只是标记一下GC root能直接关联到的对象速度很快
并发标记阶段就是进行Gc roots tracing的过程。
重新标记阶段则是为了修正并发标记期间因用户程序继续运作而导致的标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般
会比初始标记阶段稍长一些,但远比并发标记的时间短。cms收集器的运作步骤哦如下图所示,在整个过程中耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作,因此,从总体上看,cms收集器的内存回收过程是与用户线程一起并发执行的。
优点
并发收集、低停顿,oracle公司的一些官方文档中也称之为并发底停顿(concurrent low pause collector)
缺点
cms收集器对cpu资源非常敏感
cms收集器无法处理浮动垃圾(floating garbage),可能出现“concurrent mode failure”失败而导致另一次full gc的产生,如果在应用中
老年代增长不是太快,可以适当调高参数-XX:CMSInitiatingOccupancyFraction的值来提高触发百分比,以便降低内存回收次数从而获取更好的性能,要是cms运行期间预留的百分比无法满足程序需要时,这样停顿时间就很长了。所以说参数-XX:CMSInitiatingOccupancyFraction设置得太高容易很容易导致大量”concurrent mode Failure”失败,性能反而降低收集结束会有大量空间碎片产生,空间碎片过多时,将会给大对象分配带来很大麻烦,往往出现老年代还有很大空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前进行一次full gc。cms收集器提供了一个-XX:UseCMSCompactAtFullCollection开关参数(默认就是开启的),用于在cms收集器顶不住要进行full gc时开启内存碎片的合并整理过程,内存整理的过程无法并发的,空间碎片问题就没有了,但是停顿时间不得不边长。
空间分配担保
- 在发生minor gc之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么minor gc可以确保是安全的。当大量对象在minor gc后仍然存活,就需要老年代进行空间分配担保,把survivor无法容纳的对象直接进入老年代。如果老年代判断到剩余空间不足(根据以往每一次回收晋升到老年代对象容量的平均值作为经验),则进行一次full gc。
cms收集器收集步骤
- phase1:initial mark
- phase2:concurrent mark
- phase3:concurrent preclean
- phase4:concurrent abortable Preclean
- phase5:final remark
- phase6:concurrent sweep
- phase7:concurrent reset
phase1 initial mark
- 这个是cms两次stop-the-world事件的其中一次,这个阶段的目标是:标记那些直接被gc root引用或者被年轻代存活对象所引用的所有对象。
phase2:concurrent mark
- 在这个阶段garbage collecor会遍历老年代,然后标记所存活的对象,他会根据上个阶段找到gc roots遍历查找,并发标记阶段,她会与用户的应用程序并发运行。并不是老年代所有的存活对象会被标记,因为在标记期间用户的程序可能会改变一些引用
在上面的图中,与阶段一的图进行比对,就会发现有一个对象的引用已经发生了变化。
phase3:concurrent preclean
- 这也是一个并发阶段,与应用的线程并发运行,并不会stop应用的线程,在并发运行的过程中,一些对象的引用可能会发生变化,但是这种情况发生时,jvm会将包含这个对象的区域(card)标记为Dirty,这也是Card Marking
- 在pre-clean阶段,那些能够从Dirty对象到达的对象也会被标记,这个标记做完之后,dirty card标记就会被清除了
phase4:concurrent abortable Preclean
- 这也是一个并发阶段,但是同样不会影响用户的应用线程,这个阶段就是为了尽量承担stw中最终标记阶段的工作。这个阶段持续时间依赖于很多的
因素,由于这个阶段是在重复做相同的工作,直接满足一些条件(比如:重复迭代的次数、完成的工作量或时钟时间等)
phase5:final remark
- 这个是第二个stw阶段,也是cms中的最后一个,这个阶段的目标是标记老年代所有的存活对象,由于之前的阶段是并发执行的,gc线程可能跟不上
应用程序的变化,为了完成标记老年代所有存活对象的目标,stw就非常油必要了。 - 通常cms的final remark阶段会在年轻代尽可能干净的时候运行,目的是为了减少连续stw发生的可能性(年轻代存活对象多的话,也会导致老年代涉及的存活对象会很多),这个阶段会比前面的几个阶段更复杂一些
标记阶段完成
- 经历过五个阶段之后,老年代所有存活对象都被标记过了,现在可以通过清楚算法去清理那些老年代不再使用的对象。
phase6:concurrent sweep
- 这里不需要stw,它是与用户的应用程序并发运行,这个阶段是:清除那些不再使用的对象,回收它们的占用空间将来使用。
phase7:concurrent reset
- 这个阶段也是并发执行的,它会重设cms内部的数据结构,为了下次的gc做准备。
总结
- cms通过将大量的工作分散到并发处理阶段来减少stw时间,在这块做得非常优秀,但是cms也有一些其他的问题。
- cms收集器无法处理浮动垃圾(floating garbage),可能出现“concurrent mode failure”失败而导致一次full gc的产生,可能引发串行full gc;
- 空间碎片,导致无法分配大对象,cms收集器提供了一个-XX:+UseCMSCompactAtCollection 开关参数(默认就是开启的),用于在cms收集器顶不住要进行full gc时开启内存碎片的合并整理过滤,内存整理的过程是无法并发的,空间碎片问题没有了,但停顿时间不得不变长。
- 对于堆比较大应用,gc的时间难以预估。
演示
编写程序:
1 |
|
运行结果:
1 | 111111 |
通过上面程序的例子就能反证出cms收集器的一些收集的过程。