GC策略在G1还没成熟的情况下,目前主要有串行、并行和并发三种,对于大内存的应用而言,串行的性能太低,因此使用到的主要是并行和并发两种,具体这两种GC的策略在深入JVM章节中已讲解,并行和并发GC的策略通过-XX:+UseParallelGC和-XX:+UseConcMarkSweepGC来指定,还有一些细节的配置参数用来配置策略的执行方式,例如:-XX:ParallelGCThreads、-XX:CMSInitiatingOccupancyFraction等,新生代对象回收只可选择并行,在此就举例来看看两种GC策略在Full GC时的具体表现状况。
测试GC策略状况的代码如下:
/**
* @param args
*/
public static void main(String[] args) throws Exception{
System.out.println( " ready to start " );
Thread.sleep( 10000 );
List < GCPolicyDataObject > cacheObjects = new ArrayList < GCPolicyDataObject > ();
for ( int i = 0 ; i < 2048 ; i ++ ) {
cacheObjects.add( new GCPolicyDataObject( 100 ));
}
System.gc();
Thread.sleep( 1000 );
for ( int i = 0 ; i < 10 ; i ++ ) {
System.out.println( " Round: " + (i + 1 ));
for ( int j = 0 ; j < 5 ; j ++ ) {
System.out.println( " put 64M objects " );
List < GCPolicyDataObject > tmpObjects = new ArrayList < GCPolicyDataObject > ();
for ( int m = 0 ; m < 1024 ; m ++ ) {
tmpObjects.add( new GCPolicyDataObject( 64 ));
}
tmpObjects = null ;
}
}
cacheObjects.size();
cacheObjects = null ;
}
}
class GCPolicyDataObject{
byte [] bytes = null ;
GCPolicyRefObject object = null ;
public GCPolicyDataObject( int factor){
bytes = new byte [factor * 1024 ];
object = new GCPolicyRefObject();
}
}
class GCPolicyRefObject{
GCPolicyRefChildObject object;
public GCPolicyRefObject(){
object = new GCPolicyRefChildObject();
}
}
class GCPolicyRefChildObject{
public GCPolicyRefChildObject(){
;
}
}
以-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秒。
从上面的结果来看,由于CMSGC多数动作是和应用并发做的,采用CMS GC确实可以减小GC动作给应用造成的暂停,但也正因为是并发进行的,因此CMS GC需要耗费更多的CPU,因此对于CPU密集型应用而言,CMS不一定是好的选择。
在采用CMS GC的情况下,尤其要注意的是concurrentmode failure的现象,这可以通过-XX:+PrintGCDetails来观察,当出现concurrent mode failure的现象时,就意味着此时JVM将继续采用Stop-The-World的方式来进行Full GC,这种情况下,采用CMS就没什么意义了,造成concurrentmode failure的原因主要是当minor GC进行时,旧生代所剩下的空间小于Eden区域+From区域的空间,要避免这种现象,可以采用以下三种方法:
l 调低触发CMS GC执行的阀值
CMS GC触发主要由CMSInitiatingOccupancyFraction值决定,默认情况是当旧生代已用空间为68%时,即触发CMS GC。
在出现concurrentmode failure的情况下,可考虑调小这个值,提前CMS GC的触发,以保证旧生代有足够的空间。
l 扩大旧生代空间
调小新生代占用的空间或增大整个JVM Heap的空间可扩大旧生代空间,这对于避免concurrent mode failure现象可以提供很大的帮助。
l 调小CMSMaxAbortablePrecleanTime的值
CMSGC需要经过较多步骤才能完成一次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的现象。