CMS垃圾收集器

1、CMS垃圾收集器介绍
众所周知,在oracle公司的Hotspot的架构中,大致上采用分代回收的机制。其中出生代又采用了拷贝复制的方法。若是对象在初生代内存活超过必定次数以后,就能够晋升到老生代中,而CMS垃圾收集器就是专门用来对老生代作收集。
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用都集中在互联网站或B/S系统的服务端上,这类应用尤为重 视服务的响应速度,但愿系统停顿时间最短,以给用户带来较好的体验。CMS收集器就很是符合这类应用的需求。
从名字(包含“Mark Sweep”)上就能够看出CMS收集器是基于“标记-清除”算法实现的,它的运做过程相对于前面几种收集器来讲要更复杂一些,整个过程分为4个步骤,包括:
初始标记(CMS initial mark)
并发标记(CMS concurrent mark)
从新标记(CMS remark)
并发清除(CMS concurrent sweep)
其中初始标记、从新标记这两个步骤仍然须要“Stop The World”。初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,并发标记阶段就是进行GC Roots Tracing的过程,而从新标记阶段则是为了修正并发标记期间,因用户程序继续运做而致使标记产生变更的那一部分对象的标记记录,这个阶段的停顿时间一 般会比初始标记阶段稍长一些,但远比并发标记的时间短。
因为整个过程当中耗时最长的并发标记和并发清除过程当中,收集器线程均可以与用户线程一块儿工做,因此整体上来讲,CMS收集器的内存回收过程是与用户线程一块儿并发地执行的。经过下图能够比较清楚地看到CMS收集器的运做步骤中并发和须要停顿的时间。
CMS是一款优秀的收集器,它的最主要优势在名字上已经体现出来了:并发收集、低停顿,Sun的一些官方文档里面也称之为并发低停顿收集器(Concurrent Low Pause Collector)。可是CMS还远达不到完美的程度,它有如下三个显著的缺点:
  • CMS收集器对CPU资源很是敏感。其实,面向并发设计的程序都对CPU资源比较敏感。在并发阶段,它虽然不会致使用户线程停顿,可是会由于占用 了一部分线程(或者说CPU资源)而致使应用程序变慢,总吞吐量会下降。CMS默认启动的回收线程数是(CPU数量+3)/ 4,也就是当CPU在4个以上时,并发回收时垃圾收集线程最多占用不超过25%的CPU资源。可是当CPU不足4个时(譬如2个),那么CMS对用户程序 的影响就可能变得很大,若是CPU负载原本就比较大的时候,还分出一半的运算能力去执行收集器线程,就可能致使用户程序的执行速度突然下降了50%,这也 很让人受不了。为了解决这种状况,虚拟机提供了一种称为“增量式并发收集器”(Incremental Concurrent Mark Sweep / i-CMS)的CMS收集器变种,所作的事情和单CPU年代PC机操做系统使用抢占式来模拟多任务机制的思想同样,就是在并发标记和并发清理的时候让GC 线程、用户线程交替运行,尽可能减小GC线程的独占资源的时间,这样整个垃圾收集的过程会更长,但对用户程序的影响就会显得少一些,速度降低也就没有那么明 显,可是目前版本中,i-CMS已经被声明为“deprecated”,即再也不提倡用户使用。
  • CMS收集器没法处理浮动垃圾(Floating Garbage),可能出现“Concurrent Mode Failure”失败而致使另外一次Full GC的产生。因为CMS并发清理阶段用户线程还在运行着,伴随程序的运行天然还会有新的垃圾不断产生,这一部分垃圾出如今标记过程以后,CMS没法在本次 收集中处理掉它们,只好留待下一次GC时再将其清理掉。这一部分垃圾就称为“浮动垃圾”。也是因为在垃圾收集阶段用户线程还须要运行,即还须要预留足够的 内存空间给用户线程使用,所以CMS收集器不能像其余收集器那样等到老年代几乎彻底被填满了再进行收集,须要预留一部分空间提供并发收集时的程序运做使 用。在默认设置下,CMS收集器在老年代使用了68%的空间后就会被激活,这是一个偏保守的设置,若是在应用中老年代增加不是太快,能够适当调高参数 -XX:CMSInitiatingOccupancyFraction的值来提升触发百分比,以便下降内存回收次数以获取更好的性能。CMS须要较大的内存空间去运行垃圾收集,此时用户程序也在运行,要是CMS运行期 间预留的内存没法知足程序须要,就会出现一次“Concurrent Mode Failure”失败,这时候虚拟机将启动后备预案:临时启用Serial Old收集器来从新进行老年代的垃圾收集,这样停顿时间就很长了。因此说参数-XX:CMSInitiatingOccupancyFraction设置 得过高将会很容易致使大量“Concurrent Mode Failure”失败,性能反而下降。
  • 还有最后一个缺点,在本节在开头说过,CMS是一款基于“标记-清除”算法实现的收集器,若是读者对前面这种算法介绍还有印象的话,就可能想到这 意味着收集结束时会产生大量空间碎片。空间碎片过多时,将会给大对象分配带来很大的麻烦,每每会出现老年代还有很大的空间剩余,可是没法找到足够大的连续 空间来分配当前对象,不得不提早触发一次Full GC。为了解决这个问题,CMS收集器提供了一个-XX:+UseCMSCompactAtFullCollection开关参数,用于在“享受”完 Full GC服务以后额外免费附送一个碎片整理过程,内存整理的过程是没法并发的。空间碎片问题没有了,但停顿时间不得不变长了。虚拟机设计者们还提供了另一个 参数-XX: CMSFullGCsBeforeCompaction,这个参数用于设置在执行多少次不压缩的Full GC后,跟着来一次带压缩的。
2、CMS收集器测试
虚拟机参数:
-server -verbose:gc -Xms512m -Xmx512m -Xmn192m -XX:PermSize=32m -XX:MaxPermSize=32m -Xss256k -XX:+UseConcMarkSweepGC -XX:ParallelGCThreads=4 
-XX:+UseCMSCompactAtFullCollection -XX:CMSMaxAbortablePrecleanTime=5000 
-XX:CMSFullGCsBeforeCompaction=5 
-XX:CMSInitiatingOccupancyFraction=85 
-XX:+UseParNewGC -Xloggc:D:/logs/gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:/logs/HeapDumpOnOutOfMemoryError.log -XX:+DisableExplicitGC -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps

测试代码java

public class Test2 { public static void main(String[] args) { byte[] b1 = getM(50); byte[] b2 = getM(50); byte[] b3 = getM(50); byte[] b4 = getM(50); byte[] b5 = getM(50); byte[] b6 = getM(50); byte[] b7 = getM(5); byte[] b8 = getM(5); byte[] b9 = getM(5); byte[] b10 = getM(5); byte[] b11 = getM(5); byte[] b12 = getM(5); byte[] b13 = getM(5); byte[] b14 = getM(5); byte[] b15 = getM(5); byte[] b16 = getM(5); byte[] b17 = getM(5); byte[] b18 = getM(5); byte[] b19 = getM(5); byte[] b20 = getM(100); byte[] b21 = getM(100); byte[] b22 = getM(100); byte[] b23 = getM(100); } public static byte[] getM(int m) { return new byte[1024 * 1024 * m]; } }

由于用的是JDK1.8测试来测试的,因此PermSize,MaxPermSize已经在java8中移除,UseCMSCompactAtFullCollection,CMSFullGCsBeforeCompaction已通过时。 控制台输出以下:算法

上面的java测试代码能够随意进行添加修改,来测试CMS收集器的收集的各个阶段。
gc.log输出:
"C:\Program Files\Java\jdk1.8.0_111\bin\java" -Dvisualvm.id=271965950114811 -server -verbose:gc -Xms512m -Xmx512m -Xmn192m -XX:PermSize=32m -XX:MaxPermSize=32m -Xss256k -XX:+UseConcMarkSweepGC -XX:ParallelGCThreads=4 -XX:+UseCMSCompactAtFullCollection -XX:CMSMaxAbortablePrecleanTime=5000 -XX:CMSFullGCsBeforeCompaction=5 -XX:CMSInitiatingOccupancyFraction=85 -XX:+UseParNewGC -XX:+HeapDumpOnOutOfMemoryError -XX:+DisableExplicitGC -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps 0.268: [GC (Allocation Failure) 0.268: [ParNew: 114985K->1537K(176960K), 0.0314708 secs] 114985K->103939K(504640K), 0.0315495 secs] [Times: user=0.11 sys=0.02, real=0.03 secs] 0.322: [GC (Allocation Failure) 0.323: [ParNew: 158154K->1842K(176960K), 0.0473010 secs] 260556K->257847K(504640K), 0.0473510 secs] [Times: user=0.14 sys=0.03, real=0.05 secs] 0.377: [GC (CMS Initial Mark) [1 CMS-initial-mark: 256004K(327680K)] 309047K(504640K), 0.0010189 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 0.378: [CMS-concurrent-mark-start] 0.382: [CMS-concurrent-mark: 0.004/0.004 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 0.382: [CMS-concurrent-preclean-start] 0.382: [CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 0.383: [CMS-concurrent-abortable-preclean-start] 0.383: [CMS-concurrent-abortable-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 0.383: [GC (Allocation Failure) 0.383: [ParNew: 125436K->125436K(176960K), 0.0000202 secs]0.383: [CMS (concurrent mode failure): 256004K->322560K(327680K), 0.0757518 secs] 381440K->374947K(504640K), [Metaspace: 3502K->3502K(1056768K)], 0.0758187 secs] [Times: user=0.05 sys=0.02, real=0.08 secs] 0.466: [Full GC (Allocation Failure) 0.466: [CMS: 322560K->322560K(327680K), 0.0028732 secs] 477347K->477347K(504640K), [Metaspace: 3502K->3502K(1056768K)], 0.0029323 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 0.469: [Full GC (Allocation Failure) 0.469: [CMS: 322560K->322560K(327680K), 0.0203221 secs] 477347K->477315K(504640K), [Metaspace: 3502K->3502K(1056768K)], 0.0203582 secs] [Times: user=0.02 sys=0.00, real=0.02 secs] java.lang.OutOfMemoryError: Java heap space Dumping heap to java_pid41232.hprof ... 0.490: [GC (CMS Initial Mark) [1 CMS-initial-mark: 322560K(327680K)] 477315K(504640K), 0.0007328 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 0.490: [CMS-concurrent-mark-start] Heap dump file created [489779136 bytes in 9.580 secs] 10.070: [CMS-concurrent-mark: 0.002/9.579 secs] [Times: user=0.02 sys=0.42, real=9.58 secs] 10.070: [CMS-concurrent-preclean-start] 10.071: [CMS-concurrent-preclean: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 10.071: [CMS-concurrent-abortable-preclean-start] 10.071: [CMS-concurrent-abortable-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 10.071: [GC (CMS Final Remark) [YG occupancy: 157312 K (176960 K)]10.071: [Rescan (parallel) Exception in thread "main" , 0.0016064 secs]10.073: [weak refs processing, 0.0000323 secs]10.073: [class unloading, 0.0009380 secs]10.074: [scrub symbol table, 0.0009629 secs]10.075: [scrub string table, 0.0003188 secs][1 CMS-remark: 322560K(327680K)] 479872K(504640K), 0.0040970 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 10.076: [CMS-concurrent-sweep-start] 10.076: [CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 10.076: [CMS-concurrent-reset-start] 10.077: [CMS-concurrent-reset: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap par new generation   total 176960K, used 157312K [0x00000000e0000000, 0x00000000ec000000, 0x00000000ec000000) eden space 157312K, 100% used [0x00000000e0000000, 0x00000000e99a0000, 0x00000000e99a0000) from space 19648K, 0% used [0x00000000e99a0000, 0x00000000e99a0388, 0x00000000eacd0000) to space 19648K, 0% used [0x00000000eacd0000, 0x00000000eacd0000, 0x00000000ec000000) concurrent mark-sweep generation total 327680K, used 322560K [0x00000000ec000000, 0x0000000100000000, 0x0000000100000000) Metaspace used 3534K, capacity 4494K, committed 4864K, reserved 1056768K class space used 384K, capacity 386K, committed 512K, reserved 1048576K java.lang.OutOfMemoryError: Java heap space at Test2.getM(Test2.java:44) at Test2.main(Test2.java:35) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144) Java HotSpot(TM) 64-Bit Server VM warning: ignoring option PermSize=32m; support was removed in 8.0 Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=32m; support was removed in 8.0 Java HotSpot(TM) 64-Bit Server VM warning: UseCMSCompactAtFullCollection is deprecated and will likely be removed in a future release. Java HotSpot(TM) 64-Bit Server VM warning: CMSFullGCsBeforeCompaction is deprecated and will likely be removed in a future release.

主要关注一下红色框起来的部分。数据结构

http://dl2.iteye.com/upload/attachment/0113/7258/600df987-99ce-3507-af03-1513ff369f0b.png

 对上图中一条完整收集记录进行解释:架构

 http://dl2.iteye.com/upload/attachment/0113/7260/997be6c9-5e65-39a3-8d0c-c771a4c49f4b.png
[ParNew:  表示年轻代GC,用ParNew 收集器收集;
157128K->715K(176960K) :157128K 表示年轻代收集前的大小,715K表示收集后的大小,(176960K)表示当前总容量大小;
, 0.1367165 secs] :表示收集年轻代所花费的时间,单位秒;
 259530K->256719K(504640K), 0.1368421 secs]:表示当前对的收集前大小,收集后大小和容量总大小及收集花费的时间。
[Times: user=0.47 sys=0.01, real=0.14 secs]: 表示用户耗时,系统耗时,真实耗时
再回到上面大图,看红色框起来的数据:
0.279秒和0.353秒时候两次ParNew在新生代分配失败(Allocation Failure),对象直接进入老年代;
0.498秒时老年代CMS初始标记(Initial-mark);
0.498秒进行并发标记(concurrent-mark-start);
0.506秒开始进行预清理(concurrent-preclean-start);
0.667秒(CMS Final Remark);
0.693秒开始并发清理开始(concurrent-sweep);
0.833秒线程参数重置(reset);
3、CMS垃圾收集器总结
当使用CMS收集器时,当开始进行收集时,old代的收集过程以下所示:
一、首先jvm根据-XX:CMSInitiatingOccupancyFraction,-XX:+UseCMSInitiatingOccupancyOnly来决定什么时间开始垃圾收集;
二、若是设置了-XX:+UseCMSInitiatingOccupancyOnly,那么只有当old代占用确实达到了-XX:CMSInitiatingOccupancyFraction参数所设定的比例时才会触发cms gc;
三、若是没有设置-XX:+UseCMSInitiatingOccupancyOnly,那么系统会根据统计数据自行决定何时触发cms gc;所以有时会遇到设置了80%比例才cms gc,可是50%时就已经触发了,就是由于这个参数没有设置的缘由;
四、当cms gc开始时,首先的阶段是CMS-initial-mark,此阶段是初始标记阶段,是stop the world阶段,所以此阶段标记的对象只是从root集最直接可达的对象;
     [1 CMS-initial-mark: 256004K(327680K)] 307919K(504640K),,指标记时,old代的已用空间和总空间
五、下一个阶段是CMS-concurrent-mark,此阶段是和应用线程并发执行的,所谓并发收集器指的就是这个,主要做用是标记可达的对象
       此阶段会打印2条日志:CMS-concurrent-mark-start,CMS-concurrent-mark
六、下一个阶段是CMS-concurrent-preclean,此阶段主要是进行 一些预清理,由于标记和应用线程是并发执行的,所以会有些对象的状态在标记后会改变,此阶段正是解决这个问题由于以后的Rescan阶段也会stop the world,为了使暂停的时间尽量的小,也须要preclean阶段先作一部分工做以节省时间
     此阶段会打印2条日志:CMS-concurrent-preclean-start,CMS-concurrent-preclean
七、下一阶段是CMS-concurrent-abortable-preclean阶段,加入此阶段的目的是使cms gc更加可控一些,做用也是执行一些预清理,以减小Rescan阶段形成应用暂停的时间
     此阶段涉及几个参数:
     -XX:CMSMaxAbortablePrecleanTime:当abortable-preclean阶段执行达到这个时间时才会结束
     -XX:CMSScheduleRemarkEdenSizeThreshold(默认2m):控制abortable-preclean阶段何时开始执行,
      即当eden使用达到此值时,才会开始abortable-preclean阶段
     -XX:CMSScheduleRemarkEdenPenetratio(默认50%):控制abortable-preclean阶段何时结束执行
      此阶段会打印一些日志以下:
     CMS-concurrent-abortable-preclean-start,CMS-concurrent-abortable-preclean,
      CMS:abort preclean due to time XXX
八、再下一个阶段是第二个stop the world阶段了,即Rescan阶段,此阶段暂停应用线程,对对象进行从新扫描并标记;
      [YG occupancy: 102400 K (176960 K)],指执行时young代的状况
      [1 CMS-remark: 476703K(481324K)] ,指执行时old代的状况
      此外,还打印出了弱引用处理、类卸载等过程的耗时
九、再下一个阶段是CMS-concurrent-sweep,进行并发的垃圾清理
十、最后是CMS-concurrent-reset,为下一次cms gc重置相关数据结构
十一、full gc:
有2种状况会触发full gc,在full gc时,整个应用会暂停
       A,concurrent-mode-failure:当cms gc正进行时,此时有新的对象要进行old代,可是old代空间不足形成的
       B,promotion-failed:当进行young gc时,有部分young代对象仍然可用,可是S1或S2放不下,所以须要放到old代,但此时old代空间没法容纳此。
影响cms gc时长及触发的参数是如下2个: 
        -XX:CMSMaxAbortablePrecleanTime=5000
        -XX:CMSInitiatingOccupancyFraction=80
解决也是针对这两个参数来的,根本的缘由是每次请求消耗的内存量过大
解决方式:
      A,针对cms gc的触发阶段,调整-XX:CMSInitiatingOccupancyFraction=50,提前触发cms gc,就能够缓解当old代达到80%,cms gc处理不完,从而形成concurrent mode failure引起full gc
     B,修改-XX:CMSMaxAbortablePrecleanTime=500,缩小CMS-concurrent-abortable-preclean阶段的时间
     C,考虑到cms gc时不会进行compact,所以加入-XX:+UseCMSCompactAtFullCollection
       (cms gc后会进行内存的compact)和-XX:CMSFullGCsBeforeCompaction=4(在full gc4次后会进行compact)参数
相关文章
相关标签/搜索