Full GC

除直接調用System.gc外,觸發Full GC執行的狀況有以下四種。java

1. 舊生代空間不足es6

舊生代空間只有在新生代對象轉入及建立爲大對象、大數組時纔會出現不足的現象,當執行Full GC後空間仍然不足,則拋出以下錯誤:數組

java.lang.OutOfMemoryError: Java heap space

爲避免以上兩種情況引發的Full GC,調優時應儘可能作到讓對象在Minor GC階段被回收、讓對象在新生代多存活一段時間及不要建立過大的對象及數組。併發

2. Permanet Generation空間滿app

Permanet Generation中存放的爲一些class的信息等,當系統中要加載的類、反射的類和調用的方法較多時,Permanet Generation可能會被佔滿,在未配置爲採用CMS GC的狀況下會執行Full GC。若是通過Full GC仍然回收不了,那麼JVM會拋出以下錯誤信息:測試

java.lang.OutOfMemoryError: PermGen space

爲避免Perm Gen佔滿形成Full GC現象,可採用的方法爲增大Perm Gen空間或轉爲使用CMS GC。this

3. CMS GC時出現promotion failed和concurrent mode failurees5

對於採用CMS進行舊生代GC的程序而言,尤爲要注意GC日誌中是否有promotion failed和concurrent mode failure兩種情況,當這兩種情況出現時可能會觸發Full GC。spa

promotion failed是在進行Minor GC時,survivor space放不下、對象只能放入舊生代,而此時舊生代也放不下形成的;concurrent mode failure是在執行CMS GC的過程當中同時有對象要放入舊生代,而此時舊生代空間不足形成的。日誌

應對措施爲:增大survivor space、舊生代空間或調低觸發併發GC的比率,但在JDK 5.0+、6.0+的版本中有可能會因爲JDK的bug29致使CMS在remark完畢後好久才觸發sweeping動做。對於這種情況,可經過設置 -XX: CMSMaxAbortablePrecleanTime=5(單位爲ms)來避免。

4. 統計獲得的Minor GC晉升到舊生代的平均大小大於舊生代的剩餘空間

這是一個較爲複雜的觸發狀況,Hotspot爲了不因爲新生代對象晉升到舊生代致使舊生代空間不足的現象,在進行Minor GC時,作了一個判斷,若是以前統計所獲得的Minor GC晉升到舊生代的平均大小大於舊生代的剩餘空間,那麼就直接觸發Full GC。

例如程序第一次觸發Minor GC後,有6MB的對象晉升到舊生代,那麼當下一次Minor GC發生時,首先檢查舊生代的剩餘空間是否大於6MB,若是小於6MB,則執行Full GC。

當新生代採用PS GC時,方式稍有不一樣,PS GC是在Minor GC後也會檢查,例如上面的例子中第一次Minor GC後,PS GC會檢查此時舊生代的剩餘空間是否大於6MB,如小於,則觸發對舊生代的回收。

除了以上4種情況外,對於使用RMI來進行RPC或管理的Sun JDK應用而言,默認狀況下會一小時執行一次Full GC。可經過在啓動時經過- java -Dsun.rmi.dgc.client.gcInterval=3600000來設置Full GC執行的間隔時間或經過-XX:+ DisableExplicitGC來禁止RMI調用System.gc。

Full GC示例

如下經過幾個例子來演示Full GC在舊生代佔滿和CMS GC失敗時的觸發,以及不一樣Full GC時的日誌信息。

1. 舊生代空間不足觸發的Full GC

代碼以下:

public class TestFullGC{  

    public static void main(String[] args) throws Exception{  

        List<MemoryObject> objects=new ArrayList<MemoryObject>(6);  
        for(int i=0;i<10;i++){  
           objects.add(new MemoryObject(1024*1024));  
        }  
        // 讓上面的對象儘量地轉入舊生代中  
        System.gc();  
        System.gc();  
        Thread.sleep(2000);  
        objects.clear();  
        for(int i=0;i<10;i++){  
           objects.add(new MemoryObject(1024*1024));  
           if(i%3==0){  
              objects.remove(0);  
           }  
        }  
        Thread.sleep(5000);  
    }  
 }  

 class MemoryObject{  
         private byte[] bytes;  
         public MemoryObject(int objectSize){  
                 this.bytes=new byte[objectSize];  
         }  

 }

以-Xms20M -Xmx20M -Xmn10M -verbose:gc -XX:+PrintGCDetails執行以上代碼,可看到在輸出的日誌中出現了Full GC的信息:

[Full GC [PSYoungGen: 7248K->0K(8960K)] 
[PSOldGen: 9330K->4210K(10240K)] 16578K->
4210K(19200K) [PSPermGen: 1692K->1692K(16384K)], 
0.0085500 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]

上面日誌的含義爲執行了一次Full GC,新生代的方式爲並行回收GC方式,回收前內存使用了7 248KB,回收後爲0KB,可以使用的內存爲8 960KB;舊生代的方式爲並行GC方式,回收前內存使用了9 330KB,回收後爲4 210KB,可以使用的內存爲10 240KB;回收前JVM堆使用的內存爲16 578KB,回收後爲4 210KB,可以使用的內存爲19 200KB;PSPermGen回收前爲1 692KB,回收後仍然爲1 692KB,可以使用的內存爲16 384KB;回收Perm Gen消耗的時間爲7ms,整個Full GC耗費的時間爲10ms,經過System.gc調用的Full GC在日誌上會顯示爲Full GC(System)。

因爲執行以上代碼的機器爲server級機器,所以其默認採用的爲-XX:+UseParallelGC,改成採用-XX:+UseSerialGC,Full GC的信息以下:

[Full GC [Tenured: 9216K->4210K(10240K), 
0.0066570 secs] 16584K->4210K(19456K), [Perm :
1692K->1692K(16384K)], 0.0067070 secs] [Times: 
user=0.00 sys=0.00, real=0.01 secs]

上面日誌的含義爲執行了一次Full GC,舊生代的回收方式採用的爲串行方式,回收前爲9 216KB,回收後爲4 210KB,舊生代可用的內存爲10 240KB,回收耗時6ms,其餘信息則和ParallelGC時基本相同。

2. CMS GC失敗觸發的Full GC

按照CMS GC concurrent mode failure失敗的緣由,編寫了以下代碼:

public class TestCMSGC{  
   public static void main(String[] args) throws Exception{  
       List<MemoryObject> objects=new ArrayList<MemoryObject>(6);  
       for(int i=0;i<9;i++){  
          objects.add(new MemoryObject(1024*1024));  
       }  
       Thread.sleep(2000);  
       objects.remove(0);  
       objects.remove(0);  
       objects.remove(0);  
       for(int i=0;i<20;i++){  
          objects.add(new MemoryObject(1024*1024));  
          if(i%2==0){  
             objects.remove(0);  
          }  
       }  
       Thread.sleep(5000);  
   }  
}

以-Xms20M -Xmx20M -Xmn10M -verbose:gc -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC參數執行,在輸出信息中可看到相似信息:

 [Full GC [CMS[CMS-concurrent-mark: 0.004/0.006 secs] 
[Times: user=0.00 sys=0.00, real=0.00 secs]  
 (concurrent mode failure): 9331K->9331K(10240K), 
0.0183120 secs] 16499K->15475K(19456K), [CMS Perm : 
1692K->1692K(16384K)], 0.0184020 secs] [Times: 
user=0.02 sys=0.00, real=0.02 secs]

因爲是併發操做的,因此日誌有重疊。

3. 統計獲得的Minor GC晉升到舊生代的平均大小大於舊生代的剩餘空間

測試代碼以下:

public static void main(String[] args) throws Exception{  

    byte[] bytes=new byte[1024*1024*2];  
    byte[] bytes2=new byte[1024*1024*2];  
    byte[] bytes3=new byte[1024*1024*2];  
    System.out.println("ready to happen one
minor gc,if parallel scavenge gc,then should one full gc");  
    byte[] bytes4=new byte[1024*1024*2];  
    Thread.sleep(3000);  
    System.out.println("minor gc end");  
    byte[] bytes5=new byte[1024*1024*2];  
    byte[] bytes6=new byte[1024*1024*2];  
    System.out.println("minor gc again ,
and should direct full gc");  
    byte[] bytes7=new byte[1024*1024*2];  
    Thread.sleep(3000);  
}

以java -Xms20M -Xmx20M -Xmn10M -verbose:gc -XX:+PrintGCDetails-XX:+UseParallelGC執行以上代碼,可看到在輸出ready to happen one minor gc,if parallel scanvege gc,then should one full gc後,系統輸出了一次minor gc和一次full gc的信息。當輸出minor gc again,and should direct full gc時,系統執行了一次full gc,而觀看這兩次full gc的日誌信息,會看到此時舊生代、perm均沒有滿:

 
 [Full GC [PSYoungGen: 176K->0K(8960K)] [PSOldGen: 
6144K->6258K(10240K)] 6320K->6258K(19200K) [PSPermGen:
1685K->1685K(16384K)], 0.0065500 secs] [Times: 
user=0.00 sys=0.00, real=0.00 secs]  
[Full GC [PSYoungGen: 6224K->4096K(8960K)] [PSOldGen: 
6258K->8306K(10240K)] 12482K->12402K(19200K)
[PSPermGen: 1692K->1692K(16384K)], 0.0222810 
secs] [Times: user=0.01 sys=0.01, real=0.02 secs]

結合代碼以及以前介紹的觸發機制,可看到在輸出ready to happen one minor gc後,系統分配了一個2MB的對象,此時eden space空間不足,觸發minor gc,minor gc後有6MB的對象進入了舊生代,此時舊生代剩餘空間爲4MB,因而執行full gc。

在輸出minor gc again後,eden space空間再次不足,因而再度觸發minor gc,此時minor gc發現以前統計出來的晉升到舊生代對象的平均值爲6MB,並和舊生代的剩餘空間進行比較,發現舊生代空間更小,因而直接執行full gc。

當將執行參數切換爲java -Xms20M -Xmx20M -Xmn10M -verbose:gc -XX:+PrintGCDetails-XX:+UseSerialGC後,可看到系統前後執行了一次minor gc和一次full gc。

相關文章
相關標籤/搜索