摘自《構建高性能的大型分布式Java應用》第六章,感興趣的同學們可以看看。
GC策略在G1還沒成熟的情況下,目前主要有串行、并行和并發三種,對于大內存的應用而言,串行的性能太低,因此使用到的主要是并行和并發兩種,具體這兩種GC的策略在深入JVM章節中已講解,
并行和并發GC的策略通過-XX:+UseParallelGC和-XX:+UseConcMarkSweepGC來指定,還有一些細節的配置參數用來配置策略的執行方式,例如:-XX:ParallelGCThreads、-XX:CMSInitiatingOccupancyFraction等,新生代對象回收只可選擇并行,在此就舉例來看看兩種GC策略在Full GC時的具體表現狀況。
測試GC策略狀況的代碼如下:
public class GCPolicyDemo {
/**
* @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秒。
從上面的結果來看,由于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,如通過PrintGCDetails觀察到這兩步之間耗費了較長的時間,可以通過-XX: CMSMaxAbortablePrecleanTime設置較小的值,以保證CMS GC盡快完成對象的回收,避免concurrent mode failure的現象。