垃圾回收案例——GC分析

GC 分析

首先運行一段空的代碼,並設置虛擬機參數:“-Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc” 。


public class Demo {

    private static final int _512KB = 512*1024;
    private static final int _1MB = 1024*1024;
    private static final int _6MB = 6*1024*1024;
    private static final int _7MB = 7*1024*1024;
    private static final int _8MB = 8*1024*1024;

    // -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc
    // -Xms20M -Xmx20M (初始和最大的堆空間20M)
    // -Xmn10M (新生代10M)
    // -XX:+UseSerialGC(垃圾回收器)
    // -XX:+PrintGCDetails -verbose:gc(打印GC的詳情)
    public static void main(String[] args) {

    }

}

上面的運行結果爲:

無任何代碼邏輯時堆空間

 上圖可以看出堆主要分爲new generation(新生代)、tenured generation(老年代)、Metaspace(元空間)。

對於新生代來,我們在JVM參數給新生代分配了10M,但是上圖新生代的Total僅有9216K,少了1024K,是因爲默認倖存區To的空間是不能使用的,所以新生代的空間爲9216K。eden spance 佔有8M,雖然我們沒有任何的邏輯,此時的伊甸園中已經有26%的內存已經被使用,是因爲在程序運行的時候就會默認加載一些對象,這些對象都放在了伊甸園中。

那如果我們往堆內存中放入一個7M的對象又會發生什麼呢?

由圖中可以看出在插入一個7M對象的時候,進行了一次垃圾回收,而且是Minor GC(如果是Full GC則會輸出Full GC),垃圾回收中的信息DefNew 說明這個是在新生代中的進行的垃圾回收,2004K->600K(9216K)的意思是回收前的佔用爲2004K,回收後內存佔有600K然後總的內存大小爲9216K,然後用時0.0333417秒。再這之後的信息是整個堆內存的一個信息,前面是關於新生代的。堆內存在回收錢佔用2004K,回收後佔用600K整個堆內存共計19456K,堆內存的回收共計耗時爲0.0386088秒。

在輸出信息的下面就是堆內存中新生代、老年代的各個信息。可以看出From區佔用了58%的空間,雖然to佔用0%,但是在回收的過程中是先放置To區然後再交換From和To區。

如果在上面的代碼中我們在添加兩個521KB的對象又是什麼樣的效果?

添加第一個512KB對象
添加第二個512KB的對象

 

 


 

 

 

 

 

 

 很清楚的可以看出在添加第一個512KB對象的時候伊甸園的空間還是比較充足的,只是添加之後已經使用了96%,而且添加的過程中也只執行了一次GC。添加第二個512KB的對象時,伊甸園的空間很明顯是不夠的,所以再次進行了一次垃圾回收,顯然這次的回收的即使回收新生代,也不能滿足的空間需求,所以很多對象已經晉升到老年代,雖然沒有超過閾值,是因爲此時內存十分緊張。

GC 大對象

在往堆內存放入對象的時候,如果放入的對象超過了伊甸園的大小,這個時候JVM該如何處理呢?

因爲我們設置虛擬機參數的時候已經設置了伊甸園的大小爲8M,這個時候如果插入一個8M的對象,伊甸園即新生代整個區域都放不下這個對象,就算此時觸發新生代的垃圾回收,也放不下這個8M對象。這個時候如果老年代的空間足夠,會把這個大的對象直接晉升放入老年代中去,而且這種情況下不會觸發垃圾回收。

如果插入兩個8M的對象,此時很明顯堆的空間不足,所以會報錯,但是在報錯之前,JVM還是會進行挽救,會進行Full GC,而Full Gc會觸發Minor GC。

當內存溢出放在一個子線程中,並不會導致Java主線程的執行異常。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章