作者|Lunatic_Genius
编辑|包包
CMS垃圾收集器是为了那些需要更短的垃圾收集暂停时间,并且能够在应用程序运行时与垃圾收集器共享处理器资源的应用程序而设计的。
通常,拥有相对较大的长时间存活的数据(一个大的年老代)并且在运行的机器上具有两个或者更多的处理器的应用程序会从CMS垃圾收集器受益。CMS垃圾收集器可以通过命令行选项-XX:+UseConcMarkSweepGC启用。
CMS垃圾收集器已经被废弃,强烈建议考虑G1垃圾收集器替代。
CMS垃圾收集器的性能表现和结构
并发模式故障
过渡的GC time 和 OOM
CMS垃圾收集器和浮动垃圾
CMS垃圾收集暂停时间
CMS垃圾收集并发阶段
开始一个并发垃圾收集周期
计划暂停
CMS垃圾收集器衡量标准
与其他提供的垃圾收集器相似,CMS垃圾收集器是分代的。因此minor GC和major GC都会发生。CMS垃圾收集器尝试使用单独的垃圾收集器线程去跟踪可访问对象,同时执行应用程序线程,从而减少由于MAJOR GC导致的时间暂停。
在每次major GC周期中,CMS垃圾收集器在垃圾收集开始时暂停所有应用线程一段时间,然后再次暂停在垃圾收集的中间时间。垃圾收集中间时间段的停顿往往是两个停顿中较长的一个。多个线程会在两个停顿时间进行垃圾收集工作。一个或者多个垃圾收集线程执行剩余的垃圾收集工作(包含大多数跟踪的活跃对象和清除无法访问的对象)。Minor GC可以和在运行的Major GC周期交错进行,并且以类似于并行垃圾收集器的方式完成(特别是,应用程序线程在Minor垃圾收集的时候停止)。
CMS垃圾收集器使用一个或多个垃圾收集器线程,这些线程与应用线程同时运行,目的是在年老代满之前完成收集。
正如前面描述的,在普通操作中,CMS垃圾收集器在应用程序线程仍在运行的情况下执行大部分跟踪和扫描工作,因此应用程序只能看见短暂的暂停时间。但是,如果垃圾收集器无法在年老代填满之前完成无法访问对象的回收,或者如果无法满足年老代可用空间块的分配,则应用程序将暂停,并且会在所有应用程序线程停止的情况下完成垃圾收集。无法同时完成一个垃圾收集被称为并发模式故障,并表示需要调整CMS垃圾收集器参数。如果并发的垃圾收集被显示垃圾收集(System.gc())打断,垃圾收集器需要提供诊断工具信息所需要的垃圾收集信息,则并发模式打断会被报告。
CMS垃圾收集器抛出OOM异常,如果有太多的时间被花费在垃圾收集上。如果超过98%的时间花费在垃圾收集上并且少于2%的堆大小被恢复,那么OOM异常被抛出。
这个特性旨在防止应用程序长时间运行,同时由于堆大小而很少或根本没有进展。如果需要,这个特性可以通过增加选项-XX:-UseGCOverheadLimit禁止。
该策略与并行垃圾收集器中的策略相同,只是并发的执行垃圾收集的时间不计入98%的时间限制中。换句话说,只有在应用程序停止时执行的垃圾收集才会计入过多的GC time。这种垃圾收集通常由于并发模式故障或显示的垃圾收集请求(例如 System.gc())导致。
CMS垃圾收集器和Java HotSpot VM中的所有其他垃圾收集器一样,是一个跟踪垃圾收集器,他至少标识堆中所有可访问的对象。
Richard Jones和Rafael D.Lins在他们的出版物《垃圾收集:自动动态内存算法》中描述,这是一个增量更新的垃圾收集器。由于应用程序线程和垃圾收集器线程在major垃圾收集时期同时运行,垃圾收集器线程跟踪的对象可能在垃圾收集线程结束时无法访问。这种还没有回收的不可访问的对象称为浮动垃圾。浮动垃圾的数量取决于并发垃圾收集周期的持续时间和应用程序引用更新(也称为突变)的频率。此外,因为年轻代和年老代的垃圾收集都是独立的,每一个都是另一个的根源。作为一个粗略的指导,尝试将年老代的大小增加20%去处理浮动垃圾。浮动垃圾在每次垃圾收集周期都会在下一次垃圾收集周期被回收。
CMS垃圾收集器暂在一次并发垃圾收集周期中暂停应用程序两次。第一次暂停去标记可直接从根(例如,来自应用程序线程栈和寄存器的对象引用、静态对象等)和堆中其他位置(例如,年轻代)访问的对象为活动对象。
第一个停顿被称为初始标记停顿。第二个停顿出现在并发跟踪阶段的末尾,在CMS垃圾收集器完成对某个对象的跟踪之后,发现由于应用程序线程对该对象中的引用的更新而被并发跟踪丢失。第二个停顿被称为重新标记停顿。
可达对象图的并发跟踪发送在初始标记暂停和remark标记暂停之间。在这个并发标记阶段,一个或者多个垃圾收集器线程可能在使用本来提供给应用程序使用的处理器资源。因此,即使应用程序线程没有暂停,受计算量限制的应用程序可能在此或者其他并发阶段发生吞吐量明显减少。在remark暂停之后,并发扫描阶段收集标记为无法访问的对象。在垃圾收集周期完成后,CMS垃圾收集器等待,几乎不消耗计算资源,直到下一个major垃圾收集器周期开始。
对于串行垃圾收集器,每当年老代满并且在垃圾收集完时停止所有应用程序,都会发生major垃圾收集。相反,CMS垃圾收集器在并发垃圾收集开始时必须计时,以便在年老代满之前完成该垃圾收集。否则,应用程序将由于并发模式失败而观察到更长的暂停时间。这里有几种方法可以启动并发垃圾收集。
基于最近的历史记录,CMS垃圾收集器维护年老代将耗尽之前的剩余时间和并发收集周期所需时间的估计。利用这些动态估算,开始一个并发垃圾收集周期的目的是在年老代耗尽前完成垃圾收集周期。这些估值被填充,因为并发模式故障可能特别昂贵。
如果年老代的占用率超过初始占有率(年老代的百分比),则并发垃圾收集也会启动。此初始占用阈值的默认值约为92%,但是该值会随版本的变化而变化。这个值可以使用命令行选项-XX:CMSInitiatingOccupancyFraction=
年轻代和年老代的垃圾收集是独立发生的。
他们不会重叠,但可能会快速连续地发生,因此一个垃圾收集的暂停,紧接着另一个垃圾收集的暂停,可能看起来像一个单个的较长的暂停。为了避免这种情况,CMS垃圾收集器试图将remark暂停安排在上一个和下一个年轻代暂停中间位置。目前还没有对初始标记暂停执行此调度,该暂停通常比remark暂停短的多。
下面是使用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垃圾收集发生在并发垃圾收集周期。暂停初始标记指示并发垃圾收集周期的开始。以concurrent开头的行表示并发阶段的开始和结束。remark暂停是最终的暂停。没有在前面讨论的是预清理阶段,预清理代表在准备remark阶段可以同时进行的工作。最后一个阶段由concurrent reset指示,为下一个垃圾收集做准备。
初始标记暂停相对于minor垃圾收集暂停时间通常是较短的。并发阶段(并发标记,并发预清除和并发整理)通常持续比minor垃圾收集持续一个更长的暂停时间,如CMS垃圾收集器输出示例所示。但是,请注意,应用程序在这些并发阶段不会暂停。remark暂停通常在长度上可以与minor垃圾收集相提并论。这个remar暂停受某些应用程序特性的影响(例如,高速率的对象修改会增加暂停)和自上次minor垃圾收集以来的时间(例如,更多的对象在年轻代可能会增加暂停时间)。
- - - -END- - - -
喜欢本文的朋友,欢迎关注公众号 并发编程网,收看更多精彩内容