CMS垃圾收集器介绍

2015年09月26日 13:20:09

为期两个月的阿里JVM实习结束了。

在离开科大去实习之前准备了一篇关于Java5中提出的Concurrent Mark Sweep收集器的介绍。

现在贴出来:

CMS垃圾收集器
                                                            中国科学技术大学 软件学院 曾鸣堃
一.总体介绍:
       CMS是一款优秀的垃圾收集器。众所周知,在oracle公司的Hotspot的架构中,大体上采用分代回收的机制。其中出生代又采用了拷贝复制的方法。如果对象在初生代内存活超过一定次数之后,就可以晋升到老生代中,而CMS垃圾收集器就是专门用来对老生代做收集。随着现代硬件的发展,更多的企业及服务最关注的点在GC的停顿时间,而CMS恰好是一种以获取最短回收停顿时间为目标的收集器,可以给这类服务带来最好的服务效果。       
       
二.算法剖析:
Mostly-Concurrent collection
这个算法是由Boehm等人提出的,并设计了一种基于三色(tricolor)的收集器。它对整个老年代做一个写屏障,所以如果要对老年代做写操作的时候会讲对应的对象置为灰色。它主要分为以下四个阶段:
1).初始标记阶段
暂停所有的其他线程,并记录下直接与root相连的对象。
2).并发标记阶段
同时开启GC和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以GC线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。
3).最终确认标记阶段
将上一阶段做了指针更新的区域和root合并为一个伪root集合,并对其做tracing。从而可以保证真正可达的对象一定被标记了。但同时也会产生一部分被标记为可达,但其实已经是不可达的区域,由于已经没有了到达这个区域的路径,所以并没有办法将它的标志位置为0,则造成了一个暂时的内存泄漏,但这部分空间会在下一次收集阶段被清扫掉。
4).并发清扫阶段
开启用户线程,同时GC线程开始对为标记的区域做清扫。这个过程要注意不要清扫了刚被用户线程分配的对象。一个小trick就是在这个阶段,将所有新分配的对象置为可达的。  
下面我们来看一个运用了Mostly-Concurrent collection算法的例子:
   
a)处于并发标记阶段,经历了初始阶段标记的a。进入并发标记,记录了b, c, d。
b)依旧处于并发标记阶段,b和e发生了指针更新,并且被collector将其对应域置为dirty。
c)处于最终确认标记阶段,对dirty区别做一个重新扫描,并且标记上d。注意此时c已经是垃圾了,但我们无法对它的标志位做更新。
d)处于并发清扫阶段,会将f清扫掉。而c只能等到下一轮的收集,再去回收它咯。                                                                                                          
Mostly Concurrent Collection in a Generational System

       现代的JVM就是利用CMS收集器对它的老生代做收集。这中间涉及到具体的并发标记的时候,用到Card Table这个概念。因为在Hotspot JVM的世代回收过程中,新生代的空间会比较小,而老生代的空间会比较大。基于老生代空间大,变更小的特点,为了尽量减少GC引起的停顿时间,采用了停顿时间最短的CMS收集器。在CMS的并发标记的过程中,它会将整个老生代的空间切割为一个个block,每个block对应一个card。并且对整个老生代加上write barrier。从而在并发的标记过程中,用card来记录堆内发生写操作的区域。
       在具体的实现write barrier时,Hosking和Moss发现操作系统提供的内存保护屏障开销太大了,所以他们重新设计了一个只要2到3条指令就可以实现同样功能的barrier,这样意味着更小的开销,从论文里看这应该是一个轻量级的软件层面的barrier。但这种基于虚拟存储的系统并不能区分对每个block中的基本类型变量做修改还是对其中的引用类型变量做修改,所以会出现很多dirty但并没有做指针更新的block。
       由于card还是包含了很多其他信息。所以为了节省更多的空间提高局部性,又提出了Mod Union Table。将每个dirty位单独拉出来做成一个bitmap或者bytemap。从而减少空间开销,提高定位堆中dirty块的速度。
     在标记过程中,为了减少collector和mutator之间在读取Object头部而产生的相互干扰,我们并没有将mark bit放到Object的头部。而是将他们抽出来放到外部作为一个单独的数据结构。在具体的实现过程中,对heap内的每4Byte大小做一个对应的bit标记位。通常为了减少GC的停顿时间,我们会将所以root可达的对象放入到这个集合内,称为remember set。但考虑到GC的时候本身就是缺乏资源的,所以这些数据结构不应该占据大量的空间。所以具体的实现过程中只存取了与root直接相邻的对象。

三.CMS的具体过程
  • 初始标记(STW initial mark)
      在具体的实现的时候,考虑到GC停顿时间和空间利用开销的因素,我们选择了一个折中的办法。即用了一个外部的bitmap增强局部性尽量减小GC的停顿时间,同时在to-scanned-set内放了直接与root相连的object,并将其标志位置为1,这样可以减小空间的开销。注意这个阶段需要暂停所有的非GC线程。
  • 并发标记(Concurrent marking)
     在第二阶段的并发标记的阶段,GC线程会线性的扫描整个堆的bitmap。在线性的遍历bitmap的过程中,如果发现一个标志位为1的object则将它压入to-scanned-set内(具体实现用一个stack来维护)。当stack满的时候,进入循环,将栈内的对象一个个弹出来并扫描其中的引用域。同时遵循如下的规则: (1)引用的对象地址比自己低,注意此时按线性去遍历bitmap已经走过了这个位,则我们要将当前的引用对象压入栈中,并对其标志做一次更新操作置为1。(2)引用对象地址比自己高,则只用将对应的bit标记一下即可,因为根据线性的扫描,之后会扫到他,从而做有关的压栈扫描处理。从而不断的做循环,直到这个栈为空并且扫描结束,注意这个过程发生在并发的阶段,这意味着除了collector在做标记还有mutator在制造垃圾。这种策略的缺陷在于它需要对整个堆的做线性遍历,而不是简单的tracing( tracing是一个图遍历的过程,其算法复杂度为O(N+E) )。
  • 并发预清理(Concurrent Precleaning)
     为了尽量减少下一阶段STW的时间。增加了预清理的阶段,主要是对并发标记过程中留下的dirty位做一个tracing。注意这里是并发的执行,所以同时也会对card table和标记表做更新。一般实现采用不多循环遍历dirty表做tracing并对它做clean操作,直到预清理清楚了三分之一以上的dirty表则退出循环(也有的采用的是dirty数小于1000则退出循环)。
  • 重新标记(STW Remark)
     暂停所有用户线程,从dirty和root入口做tracing,确保所有可达对象做了标记。
  • 并发清理(Concurrent sweeping)
    并发的开启GC和用户线程,其中涉及到对相邻free block的整合问题。因为它可能会由mutator和collector对freeList的操作产生数据的不一致性。所以为了保证一致性,需要对空闲块的操作加上外部锁(exclusion lock)。
  • 并发重置(Concurrent reset)
     对标记的表和记录dirty的表做清零。并且重置CMS收集器的其他数据结构,等待下一次垃圾回收。

四.总结
优点:
1).将stop-the-world的时间降到最低,能给电商网站用户带来最好的体验。                
2).尽管CMS的GC线程对CPU的占用率会比较高,但在多核的服务器上还是展现了优越的特性,目前也被部署在国内的各大电商网站上。
缺点:
1).对CMS在单核和多核机器上做测试。发现CMS在收集过程中会大量占用CPU的时间。所以在第二个阶段会比较漫长,所以一般将其设置在多核机器上。并且对于CMS在单核机器上的表现设计了一套启发式控制。这种控制将收集器看作一个掠夺者,而收集器会尽量赶在用户线程分配新的对象之前完成收集的工作。同样也有可能会出现用户线程希望分配对象,但目前空间不够,则需要停下收集器,这样会让整个收集时间大大加长。所以这时候一搬会选择扩张堆的大小。
2).Mark Sweep算法一直令人诟病的碎片问题,造成了堆空间的浪费以及利用率的下降。
3).需要较大的内存空间去运行,因为在很多并行的阶段,要考虑到用户程序运行时也要分配空间。所以一般选择在堆利用率达到一个常数的时候就开启CMS的收集。可以在VM argument里来设置这个阀值。(–XX:CMSInitiatingOccupancyFraction =n,n=0~100)
4).会产生浮动垃圾,由于CMS并发清理阶段用户线程还在运行着,伴随程序自然就还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在当次收集中处理掉它们,只好等到下一次GC去处理。
 


浅谈CMS垃圾收集器与G1收集器

6、CMS收集器   CMS收集器是一种以获取最短回收停顿时间为目标的收集器。基于“标记-清除”算法实现,它的运作过程如下: 1)初始标记 2)并发标记 3)重新标记 4)并发清除   初始标记、从新...
  • oLinHao007
  • oLinHao007
  • 2015年10月04日 18:04
  • 5553

Java虚拟机学习 - 垃圾收集器

HotSpot JVM收集器               上面有7中收集器,分为两块,上面为新生代收集器,下面是老年代收集器。如果两个收集器之间存在连线,就说明它们可以搭配使用。 Seria...
  • java2000_wl
  • java2000_wl
  • 2012年10月05日 00:26
  • 27648

GC算法 垃圾收集器(包括CMS收集)

GC算法 垃圾收集器 概述 垃圾收集 Garbage Collection 通常被称为“GC”,它诞生于1960年 MIT 的 Lisp 语言,经过半个多世纪,目前已经十分成熟了。 jvm 中,...
  • a724888
  • a724888
  • 2017年03月08日 14:06
  • 694

JVMGC——并发收集器(CMS)

CMS(Concurrent Mark Sweep)收集器 C :  Concurrent M :  标记(marking)对象 :GC必须记住哪些对象可达,以便删除不可达的对象  S :  清除(...
  • hqq2023623
  • hqq2023623
  • 2016年03月27日 19:28
  • 2658

CMS(Concurrent Mark Sweep)收集器

CMS(Concurrent Mark Sweep)收集器 C :  Concurrent M :  标记(marking)对象 :GC必须记住哪些对象可达,以便删除不可达的对象  S : ...
  • jaryle
  • jaryle
  • 2016年09月04日 18:05
  • 908

CMS收集器和G1收集器优缺点

CMS收集器是一种以获取最短回收停顿时间为目标的收集器,CMS收集器是基于“”标记--清理”算法实现的,整个过程分为四个步骤:   1. 初始标记             2. 并发标记     ...
  • qq_25396633
  • qq_25396633
  • 2017年06月09日 21:57
  • 3008

JAVA垃圾收集器之CMS收集器

1、特点 CMS收集器是JAVA虚拟机中垃圾收集器的一种。它运行在JAVA虚拟机的老年代中。CMS是(Concurrent MarkSweep)的首字母缩写。CMS收集器是一种以获取最短回收停顿...
  • ffm83
  • ffm83
  • 2015年01月19日 16:14
  • 1565

了解CMS(Concurrent Mark-Sweep)垃圾回收器

1.总体介绍: CMS(Concurrent Mark-Sweep)是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。在启动JVM参...
  • w_intercool
  • w_intercool
  • 2012年06月17日 23:42
  • 8713

JVM --并发垃圾回收器CMS

1.总体介绍:CMS(Concurrent Mark-Sweep)是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。在启动JVM参数加上-...
  • yushuifirst
  • yushuifirst
  • 2015年07月14日 15:14
  • 733

CMS 垃圾回收

参考 CMS(Concurrent Mark-Sweep)是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。在启动JVM参数加上-XX:...
  • qq_17612199
  • qq_17612199
  • 2016年09月12日 20:57
  • 608
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:CMS垃圾收集器介绍
举报原因:
原因补充:

(最多只允许输入30个字)