JDK8垃圾回收调优指南--(8)CMS

原文:Java Platform, Standard Edition HotSpot Virtual Machine Garbage Collection Tuning Guide--Concurrent Mark Sweep (CMS) Collector。

并发标记清除收集器(CMS)是为了那些要求更短GC停顿的应用设计的,它能够在应用运行期间共享处理器资源。通常这种应用的“长生命周期”数据集相对较大(大型老年代)并且运行在具有两个或更多处理器的机器上更易于发挥这种收集器的优势。但是,对于任何要求低GC停顿的应用,都应该考虑使用此收集器。CMS收集器的启用命令行选项:'-XX:+UseConcMarkSweepGC'。

与其它收集器类似,CMS也是分代收集;因此'minor collection'和'major collection'都会发生。CMS尝试在应用运行期间使用特定的一些回收线程并发地跟踪可访问的对象,从而降低由于'major collection'导致的GC停顿。在每个'major collection'周期中,CMS会在开始和回收中期暂停所有应用线程一小段时间。第二次暂停往往是两次暂停中较长的一次。在两次GC停顿期间多个线程会执行回收工作。回收周期的其余部分(包括对存活对象的大量跟踪和对不可访问(不可达)对象的扫描)由一个或多个垃圾收集器线程并发地完成。'minor collection'可以与正在进行的'major collection'交错进行,类似于并行收集器的一种方式进行(尤其是,应用线程会在'minor collection'期间停顿)。

Concurrent Mode Failure

CMS收集器使用一个或多个垃圾收集器线程,这些线程与应用线程同时运行,其目标是在老年代满之前完成对其的收集。
如前所述,在正常操作中,CMS在应用线程运行的同时执行大量跟踪和清理工作,因此应用程序线程只能看到短暂的GC停顿。
然而,如果CMS收集器无法在老年代填满前回收不可到达的对象,或者老年代的空闲空间无法满足一次分配(时),应用暂停,在完成GC收集前所有应用线程将停止工作。不能并发地完成GC回收称为“并发模式故障”,这表明需要调整CMS收集器参数。如果并发回收被显式垃圾回收(System.gc())中断或因为垃圾回收需要向诊断工具提供所需信息,则会报告并发模式中断。

Excessive GC Time and OutOfMemoryError

如果太多的时间花费在垃圾回收,CMS会抛出一个OutOfMemoryError:如果超过98%的总时间花在垃圾收集上,而回收的堆不足2%,则抛出OutOfMemoryError。该特性旨在防止应用程序由于堆太小长时间无效运行。如果需要,可以通过在命令行中添加选项'-XX:-UseGCOverheadLimit'禁用此功能。

与并行收集器中的策略相同,只是执行并发回收所花费的时间不计入98%的时间限制。换句话说,只有在应用停止时执行的GC才计入98%时间限制。这种回收通常是由于并发模式失败或显式的GC请求(例如,对System.gc的调用)造成的。

Floating Garbage

CMS与Java HotSpot VM中的其它收集器一样,是一个跟踪收集器,它至少标识堆中所有可访问的对象。用Richard Jones和Rafael D. Lins在他们的出版物《Algorithms for Automated Dynamic Memory》中的话说,它是一个增量更新收集器。由于应用线程和垃圾回收线程在'major collection'中并发运行,被垃圾收集器线程跟踪的对象可能在回收过程结束时无法访问。尚未回收的这种不可访问对象称为“浮动垃圾”。“浮动垃圾”的数量取决于并发收集周期的持续时间和引用更新(也称为突变(mutations))的频率。此外,由于年轻代和老年代是独立收集的,互相作为彼此的根源。作为一个粗略的参考,可以尝试将老年代的大小增加20%,以供给“浮动垃圾”。一次并发回收周期结束时堆中的浮动垃圾将在下一次回收周期中被回收。

Pauses

CMS在并发回收周期中两次暂停应用。第一个暂停是标记根可达对象(例如,虚拟机栈中的对象引用和寄存器的对象引用、静态对象等等)和堆中的其他地方(例如,年轻代)直接可访问的对象标记为活动对象。第一个暂停称为“初始标记暂停”(initial mark pause)。第二个暂停出现在并发跟踪阶段的末期,在CMS收集器跟踪对象之后,找到那些在并发追踪阶段由于应用线程更新了引用而错过标记的对象。第二个暂停称为“二次标记暂停”(remark pause)。

Concurrent Phases

对象可达路径的并发跟踪发生在“初始标记暂停”和“二次标记暂停”之间。在这个并发跟踪阶段,一个或多个回收线程可能会使用那些对应用线程可用的空闲的处理器资源。因此,即使应用程序线程没有暂停,在此和其他并发阶段,受计算限制的应用程序的吞吐量也可能会相应的降低。在“remark pause”之后,“并发清除阶段”回收那些标识为不可到达的对象。一旦回收周期完成,CMS就会等待,几乎不消耗任何计算资源,直到下一个'major collection'周期开始。

Starting a Concurrent Collection Cycle

对于串行收集器,只要老年代已满,就会发生一次'major collection',且在回收完成之前停止所有应用线程。相反,必须在并发回收开始时进行计时,确保回收能够在老年代满之前完成;否则,应用程序由于“Concurrent model failture”长时间暂停。有几种方式可以开始一次并发回收。

根据最近的历史记录,CMS收集器维护了对老年代耗尽之前剩余的时间以及并发回收周期所需的时间的评估。通过这些动态评估,将启动一次并发回收,目的是在老年代耗尽之前完成一次并发回收周期。安全起见,对这些估计值进行填充,因为“concurrent mode failure”的代价可能非常高。

如果老年代的占用超过初始占用(老年代的一个百分比),也将启动一次并发回收。这个初始占用阈值的默认值约为92%,但是该值可能随版本的不同而变化。可以使用命令行选项'-XX:CMSInitiatingOccupancyFraction='手动调整这个值,其中是老年代大小的整数百分比(0到100)。

Scheduling Pauses

年轻代回收和老年代回收的暂停分别发生。它们不会重叠,但可能会连续发生,比如一个回收的暂停,紧接着另一个回收的暂停,就会出现一个更长的暂停。为了避免这种情况,CMS尝试将“remark pause”安排在上一次和下一次年轻代暂停之间的中间位置。这种调度目前没有为“initial mark pause”执行,“initial makr pause”通常比“remark pause”短得多。

Incremental Mode

注意,增量模式在Java SE 8中已经被弃用,可能在将来的主要版本中被删除。

CMS可以以并发阶段增量进行的模式使用。回想一下,在并发阶段,垃圾回收线程使用一个或多个处理器。增量模式的目的是通过周期性地停止并发阶段,将处理器交还给应用程序,从而减少长并发阶段的影响。这种模式在这里称为'i-cms',它将收集器并发地完成的工作划分为小块时间,这些时间安排在年轻代收集之间。当需要CMS为运行在具有少量处理器(例如,1或2)的机器上提供低GC停顿时,此特性非常有用。

并发回收周期通常包括以下步骤:

  • 停止所有应用线程,标识根可达的对象集,然后恢复所有应用线程(initial mark pause)。
  • 在应用线程运行时,使用一个或多个处理器并发地跟踪可达对象。
  • 使用一个处理器并发地跟踪自上一步跟踪以来被修改的对象。
  • 停止所有应用线程,并重新跟踪自上次检查以来可能已修改的根和对象,然后恢复所有应用程序线程(remark pause)。
  • 使用一个处理器并发地将无法访问的对象清除到分配使用的空闲列表。
  • 使用一个处理器,并发地调整堆的大小,并为下一次回收周期准备支持数据结构。

通常,CMS收在整个并发跟踪阶段使用一个或多个处理器,而不会主动放弃它们。类似地,一个处理器用于整个并发清除阶段,同样不放弃它。对于具有响应时间约束的应用程序来说,这种开销可能会造成太大的干扰,这些应用程序可能会使用处理内核,尤其是在只有一个或两个处理器的系统上运行时。增量模式通过分解并发阶段来解决这个问题,这些分解后的小并发阶段安排在'minor collection'之中(即年轻代回收)。

Command-Line Options

表8-1 列出了控制i-cms模式的命令行选项:

JDK8垃圾回收调优指南--(8)CMS_第1张图片

Recommended Options

Java SE8中使用i-cms,使用下面命令行选项:

  • -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode
  • -XX:+PrintGCDetails -XX:+PrintGCTimeStamps

前两个选项分别启用了CMS和i-cms。后两个选项是不需要的;它们只是将有关垃圾收集的诊断信息写入标准输出,以便查看和分析垃圾收集行为。

对于Java SE 5和更早的版本,Oracle建议使用以下命令行选项作为i-cms的初始设置:

  • -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode 
  • -XX:+PrintGCDetails -XX:+PrintGCTimeStamps
  • -XX:+CMSIncrementalPacing -XX:CMSIncrementalDutyCycleMin=0
  • -XX:CMSIncrementalDutyCycle=10

同样的值也推荐用于JavaSE8,尽管控制i-cms自动步调的三个选项的值已经成为JavaSE6中的默认值。

Basic Troubleshooting

'i-cms'自动调速特性使用程序运行时收集的统计数据来计算占空比,以便在堆满之前完成并发回收。然而,过去的行为并不能很好地预测将来的行为,而且这些估计可能并不总是足够准确,无法防止堆变得满。如果发生了太多的'full collection',那么尝试表8-2中的步骤,“排除i-cms自动步调特性”,一次一个:

JDK8垃圾回收调优指南--(8)CMS_第2张图片

Measurements

示例8-1,“来自CMS收集器的输出”是来自CMS收集器在使用'-verbose:gc'和'-XX:+PrintGCDetails'命令行时的输出,删除了一些"minor collection"细节。注意,CMS收集器的输出与'minor GC'的输出穿插在一起;通常,许多'minor collection'发生在并发收集周期中。CMS-initial-mark表示并发回收周期的开始,CMS-concurrent-mark表示并发标记阶段的结束,CMS-concurrent-sweep表示并发清除阶段的结束。以前没有讨论过CMS-concurrent-preclean所指的预清洗阶段。预清洗表示在CMS-remark准备阶段可以并发的完成的工作。最后一个阶段是CMS-concurrent-reset,也是下一次并发回收的准备。
示例8-1:

JDK8垃圾回收调优指南--(8)CMS_第3张图片

相对于'minor collection'的停顿时间,'initial mark'停顿通常较短。并发阶段(并发标记、并发预清理和并发清除)通常比'minor collection'停顿持续的时间长得多,如示例8-1所示。但是,请注意,应用程序不会在这些并发阶段暂停。'remark pause'通常可与'minor collection'的长度相比较。'remark pause'受某些应用程序特性的影响(例如,对象修改的高速率可能会增加此停顿),
以及自上一次'minor collection'以来的时间(例如,年轻代中的更多对象可能会增加此暂停)。

你可能感兴趣的:(java虚拟机)