并发标记清除(CMS)收集器(Concurrent Mark Sweep (CMS) Collector)是为那些追求更短的垃圾收集时间停顿,并且能够在应用程序运行时与垃圾收集器共享处理器资源的应用程序而设计的。
通常,一组相对较大的具有较长的生命周期的数据集(较大的老年代)运行在具有两个或更多处理器的机器上的应用程序将受益于此收集器的使用。CMS收集器通过命令行参数-XX:+USEconMarkSweePgc启用。
不推荐使用CMS收集器。 强烈考虑使用Garbage-First收集器(G1)。
目录
一.并发标记清除收集器(Concurrent Mark Sweep (CMS) Collector)的性能和结构
二.并发模式失败(故障)( Concurrent Mode Failure)
三. GC时间过长和内存不足错误(Excessive GC Time and OutOfMemoryError)
四.CMS收集器和浮动垃圾(Floating Garbage)
五.CMS收集器停顿
六.启动并发收集(周期)
七.调度暂停(Scheduling Pauses)
八.CMS收集器的Measurements
与其他可用的收集器相似,CMS收集器是分代的;因此,minor和major收集都会发生。CMS收集器试图通过使用独立的垃圾收集器线程,在应用程序线程执行时并发地跟踪可访问的对象,从而减少由于major收集而导致的停顿时间。
在每个major收集周期中,CMS收集器在收集开始时暂停所有应用程序线程一小段时间,并在在收集过程中再次停顿。第二次停顿往往是两次停顿中比较长的一次。多个线程在两个停顿期间进行收集工作。一个或多个垃圾收集器线程执行其余的垃圾收集(包括对活动对象的大部分追踪和对不可访问对象的扫描)。Minor收集可以与正在进行的主循环交错执行,并以类似于并行收集器的方式进行(特别地,应用程序线程在minor收集期间停止)。
CMS收集器使用一个或多个垃圾收集器线程与应用程序线程同时运行,目的是在老年代垃圾收集器满之前完成它们的收集。
如前所说的那样,在正常操作中,CMS收集器在应用程序线程仍然运行的情况下执行大部分追踪和清理工作,因此应用程序线程只能看到短暂的停顿。但是,如果CMS收集器无法在老年代填满之前完成不可访问对象的回收,或者 如果老年代中的可用空间块不能满足分配要求,则应用程序将暂停,垃圾收集工作将在所有应用程序线程都停止的情况下完成 。不能并发地完成收集工作称为并发模式故障( Concurrent Mode Failure),这表明需要调整CMS收集器参数。如果并发收集被显式垃圾收集(System.gc())或为提供诊断工具所需信息的垃圾收集中断,则会报告并发模式中断。
如果在垃圾收集中花费了太多的时间,CMS收集器将抛出OutOfMemoryError;如果总时间的98%以上花费在垃圾收集中,并且恢复的堆少于2%,则抛出OutOfMemoryError。
该特性旨在防止应用程序长时间运行,同时由于堆太小而程序很少或没有进展。如果有必要,可以通过在命令行中添加选项-XX:-UseGCOverheadLimit禁用此功能。
该策略与并行收集器中的策略相同,只是执行并发收集所花费的时间不计入98%的时间限制。换句话说,只有在应用程序停止时执行收集的时间才计入过多的GC时间。这种收集通常是由于并发模式失败或显式的集合请求(例如,对System.gc()的调用)造成的。
CMS收集器与Java HotSpot VM中的所有其他收集器一样,是一个跟踪收集器,至少它标识堆中所有可访问的对象。
Richard Jones和Rafael D. Lins在他们的出版物《垃圾收集:自动动态内存算法(Garbage Collection: Algorithms for Automated Dynamic Memory)》中指出,它是一个增量更新收集器。因为应用程序线程和垃圾收集器线程在major收集过程中同时运行,由垃圾收集器线程跟踪的对象可能在随后的收集过程结束时变得不可访问。这种尚未回收的不可访问对象称为浮动垃圾。浮动垃圾的数量取决于并发收集周期的持续时间和应用程序引用更新(也称为突变)的频率。此外,由于年轻代和老年代都是独立收集的,因此互为彼此根的来源( each acts as a source of roots to the other)。一个粗略的指导方针:尝试将老年代的大小增加20%,以解决浮动垃圾的问题。一个并发收集周期结束时堆中的浮动垃圾将在下一个收集周期中进行收集。
可访问对象图(object graph)的并发追踪发生在初始标记停顿(the initial mark pause)和标记暂停(the remark pause)之间。
在此并发追踪阶段期间,一个或多个并发垃圾收集器线程可能正在使用(可供应用程序使用的)处理器资源。 因此,即使应用程序线程没有暂停,在此阶段和其他并发阶段,计算绑定应用程序的吞吐量也会相应降低。 标记暂停(the remark pause)后,并发扫描阶段会收集标识为无法访问的对象。 收集周期完成后,CMS收集器进入等待,几乎不消耗任何计算资源,直到下一个主要收集周期开始。
对于串行收集器,当老年代(the old generation)已满,并且在收集完成前所有应用程序线程都停止时,就会发生一个major收集。相反,CMS收集器中并发收集的开始必须定时,以便在老年代满之前就完成收集工作;否则,由于并发模式失败,应用程序将观察到更长的暂停。有几种方法可以启动并发集合。 否则,应用程序会因并发模式故障而遭遇更长的停顿。 有几种方法可以启动并发收集。
基于最近的历史记录,CMS收集器维护老年代( the old generation )用尽之前剩余时间的估计值以及并发收集周期所需的时间。使用这些动态估计值,将启动一个并发收集循环,目的是在旧代耗尽之前完成收集循环。为了安全起见,对这些估计值进行了填充(往大了填充),因为并发模式失败的代价可能非常高。
如果老年代的占用超过初始占用(老年代的百分比),也会启动并发集合。这个初始占用阈值的默认值约为92%,但是该值可能随版本的不同而变化。可以使用命令行参数-XX:CMSInitiatingOccupancyFraction=
年轻代收集和老代收集的停顿是分别独立发生的。
它们不会重叠,但是可能会连续发生,这样一个收集的停顿,紧接着另一个收集的停顿,看起来可能是一个更长的停顿。为了避免这种情况,CMS收集器尝试将remark pause 安排在上一代和下一代暂停之间的中间位置。 当前没有为初始标记暂停(the initial mark pause)进行这种调度,初始标记暂停通常比remark pause短得多。
下面是CMS收集器的输出,参数是-Xlog:gc:
[121,834s][info][gc] GC(657) Pause Initial Mark 191M->191M(485M) (121,831s, 121,834s) 3,433ms
[121,835s][info][gc] GC(657) Concurrent Mark (121,835s)
[121,889s][info][gc] GC(657) Concurrent Mark (121,835s, 121,889s) 54,330ms
[121,889s][info][gc] GC(657) Concurrent Preclean (121,889s)
[121,892s][info][gc] GC(657) Concurrent Preclean (121,889s, 121,892s) 2,781ms
[121,892s][info][gc] GC(657) Concurrent Abortable Preclean (121,892s)
[121,949s][info][gc] GC(658) Pause Young (Allocation Failure) 324M->199M(485M) (121,929s, 121,949s) 19,705ms
[122,068s][info][gc] GC(659) Pause Young (Allocation Failure) 333M->200M(485M) (122,043s, 122,068s) 24,892ms
[122,075s][info][gc] GC(657) Concurrent Abortable Preclean (121,892s, 122,075s) 182,989ms
[122,087s][info][gc] GC(657) Pause Remark 209M->209M(485M) (122,076s, 122,087s) 11,373ms
[122,087s][info][gc] GC(657) Concurrent Sweep (122,087s)
[122,193s][info][gc] GC(660) Pause Young (Allocation Failure) 301M->165M(485M) (122,181s, 122,193s) 12,151ms
[122,254s][info][gc] GC(657) Concurrent Sweep (122,087s, 122,254s) 166,758ms
[122,254s][info][gc] GC(657) Concurrent Reset (122,254s)
[122,255s][info][gc] GC(657) Concurrent Reset (122,254s, 122,255s) 0,952ms
[122,297s][info][gc] GC(661) Pause Young (Allocation Failure) 259M->128M(485M) (122,291s, 122,297s) 5,797ms
CMS收集器(GC ID 657)的输出与minor收集(GC ID 658、659和660)的输出穿插在一起;通常,许多minor收集发生在并发收集周期中。Pause Initial Mark表示并发收集周期的开始。以“Concurrent”开头的行表示并发阶段的开始和结束。Pause Remark是最后的暂停。之前没有讨论的是预清洗阶段。预清理表示在准备备注阶段时可以同时进行的工作。最后一个阶段由并发重置指示,正在为下一个并发集合做准备。之前没有讨论的是预清洗阶段(the precleaning phases)。预清理表示在准备remark阶段时可以同时进行的工作。最后阶段由并发重置(Concurrent Reset )指示,并准备下一个并发收集。
初始标记暂停(The initial mark pause)通常相对于minor 收集暂停来说时间较短。并发阶段(并发标记(concurrent mark)、并发预清除(concurrent preclean)和并发扫描(concurrent sweep))通常持续的时间比minor收集暂停时间长得多,如CMS收集器输出示例所示。但是,请注意,在这些并发阶段,应用程序不会暂停。remark停顿失常通常相当于一个minor收集。The remark pause受某些应用程序特征(例如,对象修改率高会增加暂停)和自上次minor收集以来的时间(例如,年轻代中的对象过多可能会增加暂停)的影响。