圖解JVM實驗-觸發FullGC的幾個條件

1.年輕代存活的對象太多,老年代了放不下

01.示例代碼

public class DemoTest1 {
    public static void main(String[] args) {
        byte[] array1 = new byte[4 * 1024 * 1024];
        array1 = null;

        byte[] array2 = new byte[2 * 1024 * 1024];
        byte[] array3 = new byte[2 * 1024 * 1024];
        byte[] array4 = new byte[2 * 1024 * 1024];
        byte[] array5 = new byte[128 * 1024];

        byte[] array6 = new byte[2 * 1024 * 1024];

    }

複製代碼

02.啓動JVM參數

-XX:NewSize=10485760 -XX:MaxNewSize=10485760 -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:SurvivorRatio=8  -XX:MaxTenuringThreshold=15 -XX:PretenureSizeThreshold=3145728 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log
複製代碼

其中,參數-XX:PretenureSizeThreshold,參數要設置大對象閾值爲3MB,也就是超過3MB,就直接進入老年代。java

大對象大小是3MB。一旦對象大小超過3MB,不會進入新生代,直接進入老年代。shell

啓動命令:windows

java  -jar -XX:NewSize=10485760 -XX:MaxNewSize=10485760 -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:SurvivorRatio=8  -XX:MaxTenuringThre
shold=15 -XX:PretenureSizeThreshold=3145728 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log jvm-demo.jar
複製代碼

03.GC日誌

啓動以後就獲得以下GC日誌:數組

Java HotSpot(TM) 64-Bit Server VM (25.151-b12) for windows-amd64 JRE (1.8.0_151-b12), built on Sep  5 2017 19:33:46 by "java_re" with MS VC++ 10.0 (VS2010)
Memory: 4k page, physical 16703268k(7458748k free), swap 23781156k(9784196k free)
CommandLine flags: -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:MaxNewSize=10485760 -XX:MaxTenuringThreshold=15 -XX:NewSize=10485760 -XX:OldPLABSize=16 -XX:PretenureSizeThreshold=3145728 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:SurvivorRatio=8 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:-UseLargePagesIndividualAllocation -XX:+UseParNewGC 
0.174: [GC (Allocation Failure) 0.174: [ParNew (promotion failed): 7457K->8328K(9216K), 0.0046949 secs]
0.179: [CMS: 8194K->6962K(10240K), 0.0033396 secs] 11553K->6962K(19456K), [Metaspace: 2970K->2970K(1056768K)], 0.0089224 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
Heap
 par new generation   total 9216K, used 2130K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  26% used [0x00000000fec00000, 0x00000000fee14930, 0x00000000ff400000)
  from space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
  to   space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
 concurrent mark-sweep generation total 10240K, used 6962K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 2976K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 330K, capacity 386K, committed 512K, reserved 1048576K

複製代碼

04.分析GC日誌

先看以下代碼:jvm

byte[] array1 = new byte[4 * 1024 * 1024];
        array1 = null;
複製代碼

這行代碼直接分配了一個4MB的大對象,此時這個對象會直接進入老年代,接着array1再也不引用這個對象。ui

此時內存分配以下:spa

緊接着就是以下代碼3d

byte[] array2 = new byte[2 * 1024 * 1024];
        byte[] array3 = new byte[2 * 1024 * 1024];
        byte[] array4 = new byte[2 * 1024 * 1024];
        byte[] array5 = new byte[128 * 1024];
複製代碼

連續分配了4個數組,其中3個是2MB的數組,1個是128KB的數組,以下圖所示,所有會進入Eden區域中。日誌

接着會執行以下代碼:byte[] array6 = new byte[2 * 1024 * 1024];。此時還能放得下2MB的對象嗎?code

不可能了,由於Eden區已經放不下了。所以此時會直接觸發一次Young GC。

咱們看下面的GC日誌:

0.174: [GC (Allocation Failure) 0.174: [ParNew (promotion failed): 7457K->8328K(9216K), 0.0046949 secs]
複製代碼

這行日誌顯示了,Eden區原來是有7000多KB的對象,可是回收以後發現一個都回收不掉,由於上述幾個數組都被變量引用了。

因此此時,必定會直接把這些對象放入到老年代裏去,可是此時老年代裏已經有一個4MB的數組了,還能放的下3個2MB的數組和1個128KB的數組嗎?

明顯是不行的,此時必定會超過老年代的10MB大小。

因此此時看gc日誌:

0.179: [CMS: 8194K->6962K(10240K), 0.0033396 secs] 11553K->6962K(19456K), [Metaspace: 2970K->2970K(1056768K)], 0.0089224 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
複製代碼

此時執行了CMS垃圾回收器的Full GC,Full GC其實就是會對老年代進行Old GC,同時通常會跟一次Young GC關聯,還會觸發一次元數據區(永久代)的GC。

在CMS Full GC以前,就已經觸發過Young GC了,此時能夠看到此時Young GC就已經有了,接着就是執行鍼對老年代的Old GC,也就是以下日誌:

CMS: 8194K->6962K(10240K), 0.0033396 secs

這裏看到老年代從8MB左右的對象佔用,變成了6MB左右的對象佔用,這是怎麼個過程呢?

很簡單,必定是在Young GC以後,先把2個2MB的數組放入了老年代,以下圖。

此時要繼續放1個2MB的數組和1個128KB的數組到老年代,必定會放不下,因此此時就會觸發CMS的Full GC。

而後此時就會回收掉其中的一個4MB的數組,由於他已經沒人引用了,以下圖所示。

因此再看CMS的垃圾回收日誌:CMS: 8194K->6962K(10240K), 0.0033396 secs,他是從回收前的8MB變成了6MB,就是上圖所示。

最後在CMS Full GC執行完畢以後,其實年輕代的對象都進入了老年代,此時最後一行代碼要在年輕代分配2MB的數組就能夠成功了,以下圖。

05.總結

這是一個觸發老年代GC的案例,就是年輕代存活的對象太多放不下老年代了,此時就會觸發CMS的Full GC。

2.老年代可用空間小於了歷次Young GC後升入老年代的對象的平均大小

01.示例代碼

public class DemoTest1 {
    public static void main(String[] args) {
        byte[] array1 = new byte[1 * 1024 * 1024];
        array1 = null;
        byte[] array2 = new byte[1 * 1024 * 1024];
        array2 = null;
        byte[] array3 = new byte[1 * 1024 * 1024];
        array3 = null;
        byte[] array4 = new byte[1 * 1024 * 1024];//觸發YGC 1MB 1

        array1 = new byte[1 * 1024 * 1024];
        array1 = null;
        array2 = new byte[1 * 1024 * 1024];
        array2 = null;
        array3 = new byte[1 * 1024 * 1024];//觸發YGC Y 1MB O 1MB 2
        array3 = null;

        byte[] array5 = new byte[1 * 1024 * 1024];// Y 2MB O 1MB
        array1 = new byte[1 * 1024 * 1024];// Y 3MB
        array1 = null;
        array2 = new byte[1 * 1024 * 1024];// Y 1MB O 2MB YGC 3
        
        array2 = null;
        array3 = new byte[1 * 1024 * 1024];//Y 2MB O 2MB
        array3 = null;
        byte[] array6 = new byte[1 * 1024 * 1024];//Y 3MB O 2MB
        array1 = new byte[1 * 1024 * 1024];//Y 1MB O 3MB YGC 4
        
        array1 = null;
        array2 = new byte[1 * 1024 * 1024];//Y 2MB
        array2 = null;
        array3 = new byte[1 * 1024 * 1024];//Y 3MB
        array3 = null;
        byte[] array7 = new byte[1 * 1024 * 1024];//YGC 5

    }
}
複製代碼

02.啓動JVM參數

-XX:NewSize=5M -XX:MaxNewSize=5M -XX:InitialHeapSize=10M -XX:MaxHeapSize=10M -XX:SurvivorRatio=8  -XX:MaxTenuringThreshold=15 -XX:PretenureSizeThreshold=2M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log
複製代碼

其中,參數-XX:PretenureSizeThreshold,參數要設置大對象閾值爲2MB,也就是超過2MB,就直接進入老年代。

大對象大小是3MB。一旦對象大小超過3MB,不會進入新生代,直接進入老年代。

啓動命令:

java  -jar -XX:NewSize=5M -XX:MaxNewSize=5M -XX:InitialHeapSize=10M -XX:MaxHeapSize=10M -XX:SurvivorRatio=8  -XX:MaxTenuringThreshold=15 -XX:PretenureSizeThreshold=2M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log jvm-demo.jar
複製代碼

03.GC日誌

啓動以後就獲得以下GC日誌:

老年代

年輕代

Java HotSpot(TM) 64-Bit Server VM (25.151-b12) for windows-amd64 JRE (1.8.0_151-b12), built on Sep  5 2017 19:33:46 by "java_re" with MS VC++ 10.0 (VS2010)
Memory: 4k page, physical 16703268k(7221016k free), swap 23781156k(8613656k free)
CommandLine flags: -XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:MaxNewSize=5242880 -XX:MaxTenuringThreshold=15 -XX:NewSize=5242880 -XX:OldPLABSize=16 -XX:PretenureSizeThreshold=2097152 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:SurvivorRatio=8 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:-UseLargePagesIndividualAllocation -XX:+UseParNewGC 
0.121: [GC (Allocation Failure) 0.121: [ParNew: 3155K->512K(4608K), 0.0041165 secs] 3155K->766K(9728K), 0.0042644 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.125: [GC (Allocation Failure) 0.125: [ParNew: 3663K->0K(4608K), 0.0016667 secs] 3917K->1732K(9728K), 0.0017448 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.127: [GC (Allocation Failure) 0.127: [ParNew: 3142K->0K(4608K), 0.0013221 secs] 4875K->2756K(9728K), 0.0013592 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.129: [GC (CMS Initial Mark) [1 CMS-initial-mark: 2756K(5120K)] 4878K(9728K), 0.0004498 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.129: [CMS-concurrent-mark-start]
0.130: [GC (Allocation Failure) 0.130: [ParNew: 3146K->0K(4608K), 0.0005869 secs] 5902K->2756K(9728K), 0.0006362 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.131: [GC (Allocation Failure) 0.131: [ParNew: 3148K->0K(4608K), 0.0007974 secs] 5904K->3780K(9728K), 0.0008262 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 par new generation   total 4608K, used 2207K [0x00000000ff600000, 0x00000000ffb00000, 0x00000000ffb00000)
  eden space 4096K,  53% used [0x00000000ff600000, 0x00000000ff827f38, 0x00000000ffa00000)
  from space 512K,   0% used [0x00000000ffa80000, 0x00000000ffa80000, 0x00000000ffb00000)
  to   space 512K,   0% used [0x00000000ffa00000, 0x00000000ffa00000, 0x00000000ffa80000)
 concurrent mark-sweep generation total 5120K, used 3780K [0x00000000ffb00000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 2976K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 330K, capacity 386K, committed 512K, reserved 1048576K

複製代碼

04.分析GC日誌

(1).代碼塊1

先看以下代碼:

byte[] array1 = new byte[1 * 1024 * 1024];
  array1 = null;
  byte[] array2 = new byte[1 * 1024 * 1024];
  array2 = null;
  byte[] array3 = new byte[1 * 1024 * 1024];
  array3 = null;
  byte[] array4 = new byte[1 * 1024 * 1024];
複製代碼

這段代碼直接分配了4個1MB的數組,而且在第4個數組的時候,會由於新生代內存不足觸發YGC。

此時內存分配以下:

對應以下GC日誌:

0.121: [GC (Allocation Failure) 0.121: [ParNew: 3155K->512K(4608K), 0.0041165 secs] 3155K->766K(9728K), 0.0042644 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
複製代碼

此時,能夠看到新生代就只剩512K的對象,這個奇怪的512KB的對象進入Survivor From區。

那麼大小爲1MB的數組對象去哪裏呢?確定不是這個奇怪的512KB的對象。

這1MB的數組首先確定是準備進入Survivor From區,但是,在咱們設置的JVM參數下,只有0.5MB,明顯是不夠分配的。根據JVM YoungGC的規則,Survivor區放不下GC以後存活的對象,直接進入老年代

因此,1MB的數組對象是直接進入到老年代了。

此時,內存分配以下:

(2).代碼塊2

緊接這就是這塊代碼:

array1 = new byte[1 * 1024 * 1024];
 array1 = null;
 array2 = new byte[1 * 1024 * 1024];
 array2 = null;
 array3 = new byte[1 * 1024 * 1024];
複製代碼

這裏再次建立了3個1MB的數組對象,而且會觸發一次YoungGC;

對應 GC日誌以下:

0.125: [GC (Allocation Failure) 0.125: [ParNew: 3663K->0K(4608K), 0.0016667 secs] 3917K->1732K(9728K), 0.0017448 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
複製代碼

此時,Young GC以後,新生代變成0KB,那麼存活的大小爲1MB的數組對象去哪裏呢?

這1MB的數組首先確定是準備進入Survivor From區,但是,在咱們設置的JVM參數下,只有0.5MB,明顯是不夠分配的。根據JVM YoungGC的規則,Survivor區放不下GC以後存活的對象,直接進入老年代

因此,1MB的數組對象是直接進入到老年代了。

以前看到的未知的對象512KB也進入到老年代,此時內存分配以下:

(3).代碼塊3
array3 = null;
byte[] array5 = new byte[1 * 1024 * 1024];
array1 = new byte[1 * 1024 * 1024];
array1 = null;
array2 = new byte[1 * 1024 * 1024];
複製代碼

這裏再次建立了3個1MB的數組對象,而且會觸發一次YoungGC;

對應的GC日誌以下:

0.127: [GC (Allocation Failure) 0.127: [ParNew: 3142K->0K(4608K), 0.0013221 secs] 4875K->2756K(9728K), 0.0013592 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
複製代碼

此時內存分配以下:

(4).代碼塊4
array2 = null;
array3 = new byte[1 * 1024 * 1024];//Y 2MB O 2MB
array3 = null;
byte[] array6 = new byte[1 * 1024 * 1024];
array1 = new byte[1 * 1024 * 1024];
複製代碼

這裏再次建立了3個1MB的數組對象,而且會觸發一次YoungGC;而且在這兒,觸發Young GC以前觸發了一次CMS的Old GC,觸發的條件就是老年代可用空間小於了歷次Young GC後升入老年代的對象的平均大小。此時新生代大小變成0KB

對應的GC日誌以下:

0.129: [GC (CMS Initial Mark) [1 CMS-initial-mark: 2756K(5120K)] 4878K(9728K), 0.0004498 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.129: [CMS-concurrent-mark-start]
0.130: [GC (Allocation Failure) 0.130: [ParNew: 3146K->0K(4608K), 0.0005869 secs] 5902K->2756K(9728K), 0.0006362 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
複製代碼

此時內存分配以下:

(5).代碼塊5
array1 = null;
array2 = new byte[1 * 1024 * 1024];//Y 2MB
array2 = null;
array3 = new byte[1 * 1024 * 1024];//Y 3MB
array3 = null;
byte[] array7 = new byte[1 * 1024 * 1024];
複製代碼

此時,再建立3個1MB的數組對象,再次觸發一次Young GC,執行完YoungGC,此時新生代大小變成0KB;

對應的GC日誌以下:

0.131: [GC (Allocation Failure) 0.131: [ParNew: 3148K->0K(4608K), 0.0007974 secs] 5904K->3780K(9728K), 0.0008262 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
複製代碼

此時內存分配以下:

(6).總結

以下GC堆內存日誌咱們也能夠去驗證下上面的推測:

此時新生代使用了53%的大小,咱們還有一個1MB的數組,可能還存在一些未知對象。

在老年代中使用了大約3MB的空間,應該就是上圖中的對象。

Heap
 par new generation   total 4608K, used 2207K [0x00000000ff600000, 0x00000000ffb00000, 0x00000000ffb00000)
  eden space 4096K,  53% used [0x00000000ff600000, 0x00000000ff827f38, 0x00000000ffa00000)
  from space 512K,   0% used [0x00000000ffa80000, 0x00000000ffa80000, 0x00000000ffb00000)
  to   space 512K,   0% used [0x00000000ffa00000, 0x00000000ffa00000, 0x00000000ffa80000)
 concurrent mark-sweep generation total 5120K, used 3780K [0x00000000ffb00000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 2976K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 330K, capacity 386K, committed 512K, reserved 1048576K
複製代碼

3.幾個觸發Full GC的條件

第一:是老年代可用內存小於新生代所有對象的大小,若是沒開啓空間擔保參數,會直接觸發Full GC,因此通常空間擔保參數都會打開;注:jDK1.8以後已經取消了-XX:-HandlePromotionFailure機制

第二:是老年代可用內存小於歷次新生代GC後進入老年代的平均對象大小,此時會提早Full GC;

第三:是新生代Minor GC後的存活對象大於Survivor,那麼就會進入老年代,此時老年代內存不足。

上述狀況都會致使老年代Full GC。

第四:就是「-XX:CMSInitiatingOccupancyFaction」參數,

若是老年代可用內存大於歷次新生代GC後進入老年代的對象平均大小,可是老年代已經使用的內存空間超過了這個參數指定的比例,也會自動觸發Full GC。默認92%

相關文章
相關標籤/搜索