GC 策略在 G1 还没成熟的情况下,目前主要有串行、并行和并发三种,对于大内存的应用而言,串行的性能太低,因此使用到的主要是并行和并发两种,具体这两种 GC 的策略在深入 JVM 章节中已讲解, 并行和并发 GC 的策略通过 -XX:+UseParallelGC 和 -XX:+UseConcMarkSweepGC 来指定,还有一些细节的配置参数用来配置策略的执行方式,例如: -XX:ParallelGCThreads 、 -XX:CMSInitiatingOccupancyFraction 等,新生代对象回收只可选择并行,在此就举例来看看两种 GC 策略在 Full GC 时的具体表现状况。
测试 GC 策略状况的代码如下:
以 -Xms680M -Xmx680M -Xmn80M -XX:+UseConcMarkSweepGC -XX:+PrintGCApplicationStoppedTime -XX:+UseCMSCompactAtFullCollection -XX:+UseParNewGC -XX:CMSMaxAbortablePrecleanTime=5 参数执行以上代码,通过 jstat 观察到的 GC 状况如下:
共触发 39 次 minor GC ,耗时为 1.197 秒,共触发 21 次 Full GC ,耗时为 0.136 秒, GC 总耗时为 1.333 秒。
GC 动作造成应用暂停的时间为: 1.74 秒。
以 -Xms680M -Xmx680M -Xmn80M -XX:+PrintGCApplicationStoppedTime –XX:+UseParallelGC 参数执行以上代码,通过 jstat 观察到的 GC 状况如下:
共触发 119 次 minor GC ,耗时为 2.774 秒,共触发 8 次 Full GC ,耗时为 0.243 秒, GC 总耗时为 3.016 秒。
GC 动作造成应用暂停的时间为: 3.11 秒。
从上面的结果来看,由于 CMS GC 多数动作是和应用并发做的,采用 CMS GC 确实可以减小 GC 动作给应用造成的暂停,但也正因为是并发进行的,因此 CMS GC 需要耗费更多的 CPU ,因此对于 CPU 密集型应用而言, CMS 不一定是好的选择。
在采用 CMS GC 的情况下,尤其要注意的是 concurrent mode failure 的现象,这可以通过 -XX:+PrintGCDetails 来观察,当出现 concurrent mode failure 的现象时,就意味着此时 JVM 将继续采用 Stop-The-World 的方式来进行 Full GC ,这种情况下,采用 CMS 就没什么意义了,造成 concurrent mode failure 的原因主要是当 minor GC 进行时,旧生代所剩下的空间小于 Eden 区域 +From 区域的空间,要避免这种现象,可以采用以下三种方法:
l 调低触发 CMS GC 执行的阀值
CMS GC 触发主要由 CMSInitiatingOccupancyFraction 值决定,默认情况是当旧生代已用空间为 68% 时,即触发 CMS GC 。
在出现 concurrent mode failure 的情况下,可考虑调小这个值,提前 CMS GC 的触发,以保证旧生代有足够的空间。
l 扩大旧生代空间
调小新生代占用的空间或增大整个 JVM Heap 的空间可扩大旧生代空间,这对于避免 concurrent mode failure 现象可以提供很大的帮助。
l 调小 CMSMaxAbortablePrecleanTime 的值
CMS GC 需要经过较多步骤才能完成一次 GC 的动作,在 minor GC 较为频繁的情况下,很有可能造成 CMS GC 尚未完成,从而造成 concurrent mode failure ,这种情况下,减少 minor GC 触发的频率是一种方法,另外一种方法则是加快 CMS GC 执行时间,在 CMS 的整个步骤中, JDK 5.0+ 、 6.0+ 的有些版本在 CMS-concurrent-abortable-preclean-start 和 CMS-concurrent-abortable-preclean 这两步间有可能会耗费很长的时间,导致可回收的旧生代的对象很长时间后才被回收,这是 Sun JDK CMS GC 的一个 bug[1] ,如通过 PrintGCDetails 观察到这两步之间耗费了较长的时间,可以通过 -XX: CMSMaxAbortablePrecleanTime 设置较小的值,以保证 CMS GC 尽快完成对象的回收,避免 concurrent mode failure 的现象。