个人觉得官方文档介绍较为粗略,参考R大提供的信息,更多细节正在看相关GC HandBook。接下来总结。
简单翻译下,如有误读及时纠正:Concurrent Mark Sweep (CMS) Collector,并发标记-清除 收集器。
- CMS收集器是为(1. 希望gc停顿时间短,2. 能负担的起当应用程序运行时与gc共享处理器资源) 应用程序而设计的。通常(1. 拥有一个相当大的存活时间较长的数据集(一个大的老年代),2. 运行在具有双核或多核处理器的计算机上的)应用程序往往受益于该收集器。无论如何,任何有低停顿时间需求的应用都应考虑使用该收集器。CMS收集器通过命令行选项:-XX:+UseConcMarkSweepGC启用。
- 类似于其他现有的收集器,CMS收集器也是分代的。因此会分为年轻代收集和老年代收集。CMS试图在应用程序线程执行的同时,通过使用单独的垃圾收集器线程跟踪可达对象,从而减少(由于老年代收集导致的)停顿次数。在每一次老年代收集周期中,当CMS收集器开始收集时,会在一小段时间内停止所有的应用线程,然后继续进行收集。第二次停顿往往是两次停顿中较长的那个。在两次停顿期间,多个线程被用于收集工作。其余的收集工作(包括大部分对于存活对象的跟踪,不可达对象的清除)是通过与应用线程同时运行的一个活多个垃圾收集器线程完成的。年轻代的收集可以与正在老年代收集周期中交错执行,并以parallel收集器类似的方式完成(尤其是在年轻代收集时所有应用线程被停止)。
- 并发模式失败:CMS收集器使用一个或多个垃圾收集线程,这些线程与应用线程同时运行,目的是为了在老年代被填满之前完成收集工作。如前所述,在正常操作下,CMS收集器在应用线程运行的同时做了大部分跟踪和清除的工作,所以对于应用线程来说只有短暂的停顿。然而,如果CMS收集器在老年代被填满之前不能完成对不可达对象的回收,或者老年代的可分配剩余空间不能满足应用的需要,那么应用将被暂停且所有应用线程在停止的情况下完成收集。无法同时完成收集意味着并发模式失败,说明需要调整CMS收集器参数。如果并行收集被(1. 垃圾收集器显示的以System.gc(),2. 垃圾收集器需要为诊断工具提供信息)中断,则报告并发模式中断。
- GC时间过长&OutOfMemoryError:如果垃圾回收过程所花费的时间特别长,则CMS收集器会抛出一个OutOfMemoryError错误(1. 垃圾收集花费的时间大于总时间的98%,2. 小于2%的堆内存被回收)。设计该功能的目的是为防止应用程序在长时间运行中由于堆内存设置太小导致堆内存不足。如果有必要,可以通过命令行添加选项-XX:-UseGCOverheadLimit禁用该特性。
和并行收集器parallel中的策略相同,只是执行并发收集所花费的时间不计入98%限制的时间内,换句话说只有当应用程序停止时执行收集所花费的时间才被计为过多的GC时间。这种情况通常是由于并发模式失败或显示收集请求(例如一次System.gc()调用)造成的。 - 浮动垃圾:CMS收集器像Java HotSpot虚拟机中的其他收集器一样,是一个跟踪收集器,它起码能辨别出堆中所有可达对象。用Richard Jones和Rafael D. Lins出版的《Garbage Collection: Algorithms for Automated Dynamic Memory》的话说,它是一个增量更新收集器。由于应用线程和垃圾收集线程并发运行在老年代收集过程中,被垃圾收集器线程追踪的对象可能会在收集结束之后变成不可达状态,这种还没有被回收的不可达状态的对象被称为浮动垃圾。浮动垃圾的数量取决于并发收集周期持续的时间以及应用程序更新(也称为突变)的频率。此外,由于年轻代和老年代独立收集,因此对于其他代来说,他们各自都作为根的来源。作为一个粗略的参考,试着增加20%大小的老年代来解释浮动垃圾。并发收集周期结束时在堆中产生的浮动垃圾将在下一次收集周期期间被收集。
- 停顿:CMS收集器在并发收集周期期间暂停应用2次。第一次暂停是为了从(1. 根【例如:a. 应用线程栈的对象引用,b. 应用线程寄存器的对象引用,c. 静态对象等】,2. 堆中其他地方【例如:年轻代】)将直接可达对象标记为存活对象。第一次暂停称为初始标记暂停。第二次暂停出现在并发跟踪阶段的末尾,查找并行跟踪遗漏的对象(由于应用线程中被引用的对象在CMS收集器完成对象的跟踪后发生变更)。第二次暂停被称为重新标记暂停。
- 并行阶段:可达对象的并行跟踪阶段发生在初始标记暂停与重新标记暂停之间。在并行跟踪阶段,一个或多个并行垃圾收集线程与应用线程争抢处理器资源。因此在这个阶段以及其他并行阶段即使应用线程没有停止,绑定计算应用程序会发现吞吐量明显下降。重新标记停止结束后,并行清除阶段收集那些被确认的不可达状态的对象。一旦收集周期完成,CMS收集器将等待,几乎不耗费计算机资源直到开启下一次老年代收集周期。
- 开启一次并行收集周期:在串行收集器serial中,当老年代满了并且所有应用线程在收集完成时全部停止时,会出现老年代收集。正相反,并行收集的开始时间必须是老年代在满之前完成,否则由于并发模式失败,应用线程将被停止更长的时间以待观察。
基于最近的历史记录,CMS收集器在老年代耗尽所需要的时间和一次并发收集周期需要的时间之前,维持剩余预估时间。开始一次并发收集周期前,使用这些动态预估,目的是在老年代被耗尽之前完成收集周期。为了安全起见,这些预估值将被填充,因为并发模式失败的代价很昂贵。
当老年代的使用率超过初始占有率时(占老年代的百分比),并发收集也会启动。该初始占有率阈值的默认值大概是92%。但这个值可能会随着每次发布发生变化。该值可以被通过命令行选项:-XX:CMSInitiatingOccupancyFraction=<N>人工调整。该值是老年代大小(0-100)的整数百分比。 - 安排暂停:完成年轻代的停顿和老年的停顿是独立出现的。他们不会重叠,但会快速的连续发生,就好像一次收集暂停后紧接着另一次收集暂停,可能会出现单独的更长的暂停。为了避免这种情况,CMS收集器试着把重新标记大约安排在前一次与下一次年轻代收集之间。这种安排目前没有被实现在初始化标记阶段,因为初始化标记停顿时间通常比重新标记停顿时间短的多。
- 增量模式:请注意,增量模式在Java SE 8中被弃用,并且可能在未来的主要版本中被删除。
CMS收集器被用于并行阶段逐步完成的模式。回想一下,在并行期间,垃圾收集器线程使用一个或多个处理器。增量模式是为了减少因周期性停止并发阶段长时间占用处理器所带来的影响,并把处理器还给应用程序使用。这种模式在这被称作i-cms,将收集器的工作拆分成小块时间来完成,并将这些小块时间的工作安排在年轻代收集期间完成。当应用程序需要低停顿时间从而选择了CMS收集器,运行该应用程序的机器处理器数量较少(例如:1个或2个),这个功能就非常有用。
并发收集器周期通常包括以下几步:
1. 停止所有应用线程,从根处确认可达对象集合,然后恢复所有应用线程。
2. 当应用线程在执行的同时,使用一个或多个处理器并发的跟踪可达对象的曲线。
3. 使用一个处理器,重新跟踪部分对象曲线(自上一步跟踪开始有所更改的对象)。
4. 停止所有应用线程,重新跟踪部分根及对象曲线(自最近一次检查可能发生改变的对象),然后恢复所有应用线程。
5. 使用一个处理器,并发清除不可达对象,释放被分配的空间至空闲列表。
6. 使用一个处理器,并发调整堆大小并为下次收集周期准备好数据结构的支持。
通常CMS收集器在整个并发跟踪阶段使用一个或多个处理器而不主动释放它们。同理,在整个并发清楚阶段使用一个处理器而不释放它。对于响应时间有限制的应用程序来说,这种开销会带来更多的中断,否则应用程序会使用处理核心,尤其是应用程序运行在只有1个或2个处理器系统上时。增量模式通过将并行阶段拆分成多个小任务并安排在两次年轻代停顿的中间完成来解决这个问题。
增量模式是在CMS收集器自愿放弃处理器前通过占空比来控制CMS收集器允许使用处理器工作的数量,占空比是时间百分比(在CMS收集器被允许在两个年轻代收集时运之间)。增量模式可以基于应用程序行为自动估算占空比(推荐的方法被称为调步),亦或可以在命令行中将占空比设置成一个固定值。
命令行选项(i-cms模式):
Option | Description | Default Value, Java SE 5 and Earlier | Default Value, Java SE 6 and Later |
---|---|---|---|
| Enables incremental mode. Note that the CMS collector must also be enabled (with | disabled | disabled |
| Enables automatic pacing. The incremental mode duty cycle is automatically adjusted based on statistics collected while the JVM is running. | disabled | disabled |
| The percentage (0 to 100) of time between minor collections that the CMS collector is allowed to run. If | 50 | 10 |
| The percentage (0 to 100) that is the lower bound on the duty cycle when | 10 | 0 |
| The percentage (0 to 100) used to add conservatism when computing the duty cycle | 10 | 10 |
| The percentage (0 to 100) by which the incremental mode duty cycle is shifted to the right within the period between minor collections. | 0 | 0 |
| The percentage (0 to 100) used to weight the current sample when computing exponential averages for the CMS collection statistics. | 25 | 25 |
推荐初始化配置:
Java SE 8:前两个选项分别是开启CMS收集器和开启i-cms模式,后两个选项不是必选的,他们只是将关于垃圾收集相关的诊断信息写入到标准输出,以供垃圾收集器的行为被查看及分析。
-XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode \ -XX:+PrintGCDetails -XX:+PrintGCTimeStamps
Java SE 5 :oracle推荐使用下列初始化值在命令行中设置i-cms
-XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode \ -XX:+PrintGCDetails -XX:+PrintGCTimeStamps \ -XX:+CMSIncrementalPacing -XX:CMSIncrementalDutyCycleMin=0 -XX:CMSIncrementalDutyCycle=10
尽管JavaSE6中将控制i-cms的三个自动调步的选项设置了默认值,但还是推荐使用与JavaSE8相同的值。
故障排除:
i-cms的自动调步功能通过使用程序运行时收集到的统计信息来计算占空比,以便并发回收在堆满之前完成。然而过去的行为并不能完美的预测未来的行为,而且预估值并不足够准确来防止堆变满。如果要想出现更多的完全收集,尝试如下步骤,依次调整。
Step | Options |
---|---|
1. Increase the safety factor. |
|
2. Increase the minimum duty cycle. |
|
3. Disable automatic pacing and use a fixed duty cycle. |
|
测量:
通过使用-verbose:gc和-XX:+PrintGCDetails选项从CMS收集器中输出如下信息,其中删除了一些次要信息。注意:CMS收集器输出信息中夹杂着新生代收集信息的输出。通常多次新生代收集会发生在老年代收集周期时。CMS-initial-mark意味着并发收集周期的开始,CMS-concurrent-mark意味着并发标记阶段结束,CMS-concurrent-sweep意味着并发清除阶段结束, CMS-concurrent-preclean意味着之前未讨论的预清除阶段。预清除代表在CMS-remark准备重新标记阶段前并发完成。CMS-concurrent-reset意味着最后阶段并且为下一次并发收集做准备。
[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 ]
通常初始标记暂停相较于年轻代收集暂停时间较短。通常并发阶段(并发标记、并发预清除、并发清除)持续时间明显大于年轻代收集停顿时间。但是,请注意,在这些并发阶段应用不停止。重新标记停顿时间常常与新生代收集时间相当。重新标记停顿明确受应用程序特征影响(例如:对于一个对象的高频修改会增加停顿时间)和从最后一次年轻代收集的时间影响(例如:年轻代中有大量的对象也会增加停顿时间)。