并发标记扫描(CMS)收集器
并发标记扫描(CMS)收集器是为那些希望较短的垃圾收集暂停并且可以在应用程序运行时与垃圾收集器共享处理器资源的应用程序而设计的。通常,具有相对较长的长期数据集(大量使用期限)并且在具有两个或多个处理器的计算机上运行的应用程序往往会受益于此收集器的使用。但是,对于暂停时间要求较低的任何应用程序,都应考虑使用该收集器。CMS收集器通过命令行选项启用-XX:+UseConcMarkSweepGC
。
与其他可用的收集器类似,CMS收集器是世代相传的。因此,次要收藏和主要收藏都发生了。CMS收集器尝试通过使用单独的垃圾收集器线程在执行应用程序线程的同时并跟踪可访问对象,来减少由于主要收集而导致的暂停时间。在每个主要的收集周期中,CMS收集器会在收集开始时暂停所有应用程序线程一小段时间,然后再将其暂停到收集中间。第二个停顿往往是两个停顿中较长的一个。在两个暂停期间都使用多个线程来执行收集工作。
并发模式故障
CMS收集器使用一个或多个垃圾收集器线程,这些垃圾收集器线程与应用程序线程同时运行,目的是在使用期限生成完成之前完成其收集。如前所述,在正常操作中,CMS收集器在应用程序线程仍在运行的情况下执行其大部分跟踪和清除工作,因此应用程序线程仅会看到短暂的暂停。但是,如果CMS收集器无法在使用权产生的一代填满之前完成对无法访问的对象的回收,或者如果使用权能生成的可用空闲空间块无法满足分配要求,则暂停应用程序,并使用所有应用程序线程均已停止。无法同时完成收集的过程称为_并发模式失败_,表示需要调整CMS收集器参数。如果并发收集被显式垃圾收集(System.gc()
)中断,或者需要为诊断工具提供信息的垃圾收集中断,则将报告并发模式中断。
过多的GC时间和OutOfMemoryError
CMS收集器将OutOfMemoryError
在垃圾收集上花费太多时间:如果在垃圾收集中花费了总时间的98%以上,而回收不到2%的堆,则OutOfMemoryError
抛出。此功能旨在防止应用程序长时间运行,而由于堆太小而几乎没有进展,甚至没有进展。如有必要,可以通过-XX:-UseGCOverheadLimit
在命令行中添加选项来禁用此功能。
该策略与并行收集器中的策略相同,除了执行并发收集所花费的时间不计入98%的时间限制。换句话说,只有在应用程序停止时执行的收集才计入过多的GC时间。此类收集通常是由于并发模式故障或显式收集请求(例如对的调用System.gc
)引起的。
浮动垃圾
与Java HotSpot VM中的所有其他收集器一样,CMS收集器是一个跟踪收集器,它至少标识堆中的所有可访问对象。在Richard Jones和Rafael D. Lins的出版物《垃圾收集:自动动态内存算法》中,它是一个增量更新收集器。由于应用程序线程和垃圾收集器线程在主收集期间同时运行,因此垃圾收集器线程跟踪的对象随后可能会在收集过程结束时变得不可访问。尚未回收的此类无法访问的对象称为浮动垃圾。漂浮垃圾_量取决于并发收集周期的持续时间以及应用程序更新参考更新的频率(也称为_突变)。此外,由于年轻一代和终身一代是独立收集的,因此每个人都是彼此的根源。作为粗略的指导,尝试将永久代的大小增加20%,以解决浮动垃圾的问题。在一个并发收集周期结束时,将在下一个收集周期中收集堆中的浮动垃圾。
暂停
CMS收集器在并发收集周期中两次暂停应用程序。第一个暂停是将可从根直接访问的对象(例如,来自应用程序线程堆栈和寄存器的对象引用,静态对象等)和从堆中其他位置(例如,年轻代)直接标记为活动状态。此第一个停顿称为_初始标记停顿_。第二个暂停是在并发跟踪阶段的末尾,并查找由于CMS收集器完成对对象的引用后,应用程序线程对对象中的引用进行了更新而导致并发跟踪遗漏的对象。第二个暂停称为_备注暂停_。
并发阶段
可达对象图的并发跟踪发生在初始标记暂停和注释暂停之间。在此并发跟踪阶段,一个或多个并发垃圾收集器线程可能正在使用处理器资源,否则这些资源将可供应用程序使用。结果,即使没有暂停应用程序线程,在此阶段和其他并发阶段中,受计算绑定的应用程序的应用程序吞吐量也可能会相应下降。备注暂停后,并发清除阶段将收集标识为不可访问的对象。收集周期完成后,CMS收集器将等待,几乎不消耗任何计算资源,直到下一个主要收集周期开始。
开始并发收集周期
使用串行收集器时,只要保有期限的生成已满并且在完成收集时所有应用程序线程都将停止,就会发生主要收集。相反,并发收集的开始必须定时,以使收集可以在终身代变满之前完成。否则,由于并发模式故障,应用程序将观察到更长的暂停。有几种启动并发收集的方法。
根据最近的历史记录,CMS收集器将保留对永久性代用尽之前的剩余时间以及并发收集周期所需时间的估计。使用这些动态估计,开始并发的收集周期,目的是在使用权产生之前用完收集周期。为了安全起见,对这些估计值进行了填充,因为并发模式故障的代价可能很高。
如果使用年限的一代的占用量超过初始使用量(占使用年限的百分比),则并发收集也将开始。此初始占用阈值的默认值约为92%,但是该值可能会因版本而异。可以使用命令行选项手动调整此值-XX:CMSInitiatingOccupancyFraction=``<N>
,其中<N>
是占位世代大小的整数百分比(0到100)。
安排暂停
年轻代收藏和终身代收藏的暂停独立发生。它们不会重叠,但可能会快速连续发生,因此一个集合的暂停,紧接着是另一个集合的暂停,可能看起来像是一个较长的暂停。为了避免这种情况,CMS收集器尝试在上次和下一个年轻暂停之间的大致中间时间安排注释暂停。当前尚未为初始标记暂停执行此计划,该时间通常比标记暂停短得多。
增量模式
请注意,Java SE 8中不推荐使用增量模式,并且在将来的主要版本中可能会删除它。
CMS收集器可以在并发阶段以增量方式完成的模式下使用。回想一下,在并发阶段,垃圾收集器线程正在使用一个或多个处理器。增量模式旨在通过定期停止并发阶段以使处理器退还给应用程序来减轻长时间的并发阶段的影响。这种模式在这里称为_i-cms_,它将收集器同时完成的工作划分为年轻一代收集之间安排的一小段时间。当需要CMS收集器提供的低暂停时间的应用程序在具有少量处理器(例如1或2)的计算机上运行时,此功能很有用。
并发收集周期通常包括以下步骤:
-
停止所有应用程序线程,从根目录确定可访问的对象集,然后继续所有应用程序线程。
-
在应用程序线程正在执行的同时,使用一个或多个处理器跟踪可访问对象图。
-
同时使用一个处理器回溯自上一步中的跟踪以来修改的对象图的各个部分。
-
停止所有应用程序线程,并回溯自上次检查以来可能已被修改的根和对象图中的部分,然后恢复所有应用程序线程。
-
同时使用一个处理器将无法访问的对象清除到用于分配的空闲列表中。
-
同时调整堆的大小,并使用一个处理器为下一个收集周期准备支持数据结构。
通常,CMS收集器在整个并发跟踪阶段使用一个或多个处理器,而不会自愿放弃它们。同样,一个处理器用于整个并发扫描阶段,而不会放弃它。对于具有响应时间限制的应用程序(否则可能已经使用处理核心)的应用程序,这种开销可能会造成很大的破坏,尤其是在仅具有一个或两个处理器的系统上运行时。增量模式通过将并发阶段分解为短暂的活动突发来解决此问题,这些活动计划在较小的暂停之间进行。
i-cms模式使用占空比控制自愿放弃处理器之前允许CMS收集器执行的工作量。该_占空比_是时间新生代收集之间的百分比,该CMS收集器允许运行。i-cms模式可以根据应用程序的行为自动计算占空比(推荐的方法,称为_自动定步_),也可以在命令行上将占空比设置为固定值。
命令行选项
表8-1“ i-cms的命令行选项”列出了控制i-cms模式的命令行选项。“ 推荐的选项 ”部分建议了一组初始选项。
表8-1 i-cms的命令行选项
选项 | 描述 | 默认值,Java SE 5和更低版本 | 默认值,Java SE 6和更高版本 |
---|---|---|---|
-XX:+CMSIncrementalMode | 启用增量模式。请注意,也必须启用CMS收集器(使用-XX:+UseConcMarkSweepGC),此选项才能起作用。 | disabled | disabled |
-XX:+CMSIncrementalPacing | 启用自动定步。增量模式占空比根据JVM运行时收集的统计信息自动调整。 | disabled | disabled |
-XX:CMSIncrementalDutyCycle= | 允许CMS收集器运行次要收集之间的时间百分比(0到100)。如果CMSIncrementalPacing启用,则这只是初始值。 | 50 | 10 |
-XX:CMSIncrementalDutyCycleMin= | CMSIncrementalPacing启用时,占空系数下限的百分比(0到100)。 | 10 | 0 |
-XX:CMSIncrementalSafetyFactor= | 计算占空比时用于增加保守性的百分比(0到100) | 10 | 10 |
-XX:CMSIncrementalOffset= | 在次要收集之间的时间段内,增量模式占空比向右移动的百分比(0到100)。 | 0 | 0 |
-XX:CMSExpAvgFactor= | 计算CMS收集统计信息的指数平均值时,用于加权当前样本的百分比(0到100)。 | 25 | 25 |
推荐选项
要在Java SE 8中使用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的默认值。
基本故障排除
i-cms自动调整功能使用程序运行时收集的统计信息来计算占空比,以便并发收集在堆变满之前完成。但是,过去的行为并不是未来行为的完美预测,并且估计值可能并不总是足够准确以防止堆变满。如果出现了太多的完整集合,请尝试一次在表8-2“对i-cms自动起步功能进行故障排除”中的步骤。
表8-2对i-cms自动起步功能进行故障排除
Step | 选件 |
---|---|
1.增加安全系数。 | -XX:CMSIncrementalSafetyFactor= |
2.增加最小占空比。 | -XX:CMSIncrementalDutyCycleMin= |
3.禁用自动起搏,并使用固定的占空比。 | -XX:-CMSIncrementalPacing -XX:CMSIncrementalDutyCycle= |
测量
例8-1“ CMS收集器的输出”是CMS收集器的输出,带有选项-verbose:gc
和-XX:+PrintGCDetails
,删除了一些小细节。请注意,CMS收集器的输出已散布在次要收集器的输出中。通常在并发收集周期中会发生许多次要收集。CMS-initial-mark指示并发收集周期的开始,CMS-concurrent-mark指示并发标记阶段的结束,而CMS-concurrent-sweep指示并发清除阶段的结束。CMS-concurrent-preclean表示预清洁阶段,以前没有讨论过。预清洁代表可以在准备备注阶段CMS-mark的同时执行的工作。最终阶段由CMS-concurrent-reset指示,并且正在准备下一个并发收集。
示例8-1 CMS收集器的输出
[GC [1 CMS-initial-mark: 13991K(20288K)] 14103K(22400K), 0.0023781 secs]
[GC [DefNew: 2112K->64K(2112K), 0.0837052 secs] 16103K->15476K(22400K), 0.0838519 secs]
...
[GC [DefNew: 2077K->63K(2112K), 0.0126205 secs] 17552K->15855K(22400K), 0.0127482 secs]
[CMS-concurrent-mark: 0.267/0.374 secs]
[GC [DefNew: 2111K->64K(2112K), 0.0190851 secs] 17903K->16154K(22400K), 0.0191903 secs]
[CMS-concurrent-preclean: 0.044/0.064 secs]
[GC [1 CMS-remark: 16090K(20288K)] 17242K(22400K), 0.0210460 secs]
[GC [DefNew: 2112K->63K(2112K), 0.0716116 secs] 18177K->17382K(22400K), 0.0718204 secs]
[GC [DefNew: 2111K->63K(2112K), 0.0830392 secs] 19363K->18757K(22400K), 0.0832943 secs]
...
[GC [DefNew: 2111K->0K(2112K), 0.0035190 secs] 17527K->15479K(22400K), 0.0036052 secs]
[CMS-concurrent-sweep: 0.291/0.662 secs]
[GC [DefNew: 2048K->0K(2112K), 0.0013347 secs] 17527K->15479K(27912K), 0.0014231 secs]
[CMS-concurrent-reset: 0.016/0.016 secs]
[GC [DefNew: 2048K->1K(2112K), 0.0013936 secs] 17527K->15479K(27912K), 0.0014814 secs
]
相对于次要收集暂停时间,初始标记暂停通常较短。并发阶段(并发标记,并发预清理和并发扫描)通常持续的时间明显长于次要收集暂停,如示例8-1“ CMS收集器的输出”所示。但是请注意,在这些并发阶段中不会暂停应用程序。备注停顿的长度通常可与次要收藏相媲美。备注暂停受某些应用程序特性的影响(例如,对象修改率高可能会增加此暂停)以及自上次次要收集以来的时间(例如,年轻一代中的更多对象可能会增加此暂停)。