1. 程式人生 > >JVM學習之GC流程和GC策略

JVM學習之GC流程和GC策略

GC流程

對於GC流程裡面需要處理的是年輕代和老年代的記憶體空間,而元空間(永久代)都不在GC範疇。

這裡寫圖片描述

  • 1、現在有一個新物件產生,那麼物件一定需要記憶體空間,於是現在需要為該物件進行記憶體空間的申請。
  • 2、首先會判斷伊甸園區是否有記憶體空間,如果此時有充足記憶體空間,則直接將新物件儲存到伊甸園區。
  • 3、但是如果此時伊甸園區的記憶體空間不足,那麼會自動執行Minor GC操作,將伊甸園區無用的記憶體空間進行清理。清理之後會繼續判斷伊甸園區空間是否充足?如果充足,則將新的物件直接在伊甸園區進行記憶體空間分配。
  • 4、如果執行Minor GC之後伊甸園區空間依然不足,那麼這個時候會進行存活區判斷,如果存活區有剩餘空間,則將伊甸園區的部分活躍物件儲存在存活區,隨後繼續判斷伊甸園區的記憶體空間是否充足,如果充足,則進行記憶體空間分配。
  • 5、如果此時存活區也沒有記憶體空間了,則繼續判斷老年區,如果此時老年區的空間充足,則將存活區中的活躍物件儲存到老年區,而後存活區應付出現空餘空間,隨後伊甸園區將部分活躍物件儲存地存活區中,最後在伊甸園區為新物件分配記憶體空間。
  • 6、如果這個時候老年代記憶體空間也滿了,那麼這個時候將產生Major GC(Full GC)。然後再將存活區中的活躍物件儲存到老年區,從而騰出空間,然後再將伊甸園區的部分活躍物件儲存到存活區,最後在伊甸園區為新物件分配記憶體空間。
  • 7、如果老年代執行Full GC之後依然空間依然不足,應付產生OOM(OutOfMemoryError)異常。

觀察GC過程:

public
static void main(String[] args) { Random random = new Random(); String str = "show GC"; while (true) { str += str + random.nextInt(999); str.intern(); // 強制產生垃圾 } }

記憶體大小最大設定為10M,方便觀察,結果:

[GC (Allocation Failure) [PSYoungGen: 2048K->504K(2560K)] 2048K->887K(9728K), 0.0008724
secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 2355K->504K(2560K)] 2738K->1275K(9728K), 0.0008424 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 2433K->504K(2560K)] 3205K->2371K(9728K), 0.0005561 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 2205K->504K(2560K)] 4072K->3642K(9728K), 0.0007300 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 2206K->496K(2560K)] 7007K->6176K(9728K), 0.0006085 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (Ergonomics) [PSYoungGen: 496K->0K(2560K)] [ParOldGen: 5680K->1842K(7168K)] 6176K->1842K(9728K), [Metaspace: 3539K->3539K(1056768K)], 0.0052348 secs] [Times: user=0.08 sys=0.00, real=0.01 secs] [Full GC (Ergonomics) [PSYoungGen: 871K->0K(2560K)] [ParOldGen: 6831K->5021K(7168K)] 7702K->5021K(9728K), [Metaspace: 3541K->3541K(1056768K)], 0.0057354 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] [Full GC (Ergonomics) [PSYoungGen: 1743K->0K(2560K)] [ParOldGen: 6684K->4188K(7168K)] 8428K->4188K(9728K), [Metaspace: 3541K->3541K(1056768K)], 0.0041574 secs] [Times: user=0.09 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] 4188K->4188K(8704K), 0.0002199 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (Allocation Failure) Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:3332) at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124) at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448) at java.lang.StringBuilder.append(StringBuilder.java:136) at com.huoq.test.Test.main(Test.java:22) [PSYoungGen: 0K->0K(1536K)] [ParOldGen: 4188K->4168K(7168K)] 4188K->4168K(8704K), [Metaspace: 3541K->3541K(1056768K)], 0.0055203 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap PSYoungGen total 1536K, used 61K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000) eden space 1024K, 5% used [0x00000000ffd00000,0x00000000ffd0f498,0x00000000ffe00000) from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000) to space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000) ParOldGen total 7168K, used 4168K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000) object space 7168K, 58% used [0x00000000ff600000,0x00000000ffa12140,0x00000000ffd00000) Metaspace used 3573K, capacity 4502K, committed 4864K, reserved 1056768K class space used 391K, capacity 394K, committed 512K, reserved 1048576K Process finished with exit code 1

可以看到年輕代空間不足觸發Minor GC,多次Minor GC後空間仍然不足會觸發老年代的空間回收,老年代空間分配失敗會觸發Full GC,多次Full GC之後空間仍然不足就會導致“OutOfMemoryErroy”。

GC策略

這裡寫圖片描述

一、新生代可用GC策略

  • 序列GC
    這裡寫圖片描述
  • 並行回收GC
    這裡寫圖片描述
    Stop The World:簡稱“STW”,在年輕代使用並行GC處理的時候,會產生一個“STW”暫停,在進行物件 回收的時候所有其他執行緒將被暫時性掛起。
  • 並行GC
    這裡寫圖片描述

二、老年代可用GC策略

  • 序列GC
    這裡寫圖片描述
  • 並行GC
    這裡寫圖片描述
  • 併發GC(CMS GC)
    這裡寫圖片描述
    這裡寫圖片描述

三、GC策略引數調整:

這裡寫圖片描述

四、垃圾收集器引數調整策略

這裡寫圖片描述

五、測試GC策略

測試程式碼


String str = "showGCStrategy";
while (true) {
str += str + str;
str.intern();  // 強制產生垃圾
}
  • 1、預設策略
    新增虛擬機器引數:-Xmx10m -Xms10m -XX:+PrintGCDetails
    執行結果中取出一段Full GC:

    [Full GC (Ergonomics) 
    [PSYoungGen: 1606K->0K(2560K)]
    [ParOldGen: 6600K->2755K(7168K)]
    8207K->2755K(9728K), [Metaspace: 3514K->3514K(1056768K)], 0.0026280 secs] [Times: user=0.08 sys=0.00, real=0.00 secs] 

    可以發現預設情況下:

    • PSYoungGen:新生代使用的是PS(並行回收策略)
    • ParOldGen:老年代使用的是Par(並行GC)
  • 2、使用序列GC策略

    新增虛擬機器引數:-Xmx10m -Xms10m -XX:+UseSerialGC -XX:+PrintGCDetails 執行結果中取出一段:

    [GC (Allocation Failure) 
    [DefNew: 1667K->1667K(3072K), 0.0000124 secs]
    [Tenured: 6102K->2781K(6848K), 0.0024193 secs]
    7770K->2781K(9920K), [Metaspace: 3514K->3514K(1056768K)], 0.0024630 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

    可以發現:

    • DefNew:單執行緒序列GC
    • Tenured:單執行緒序列GC
  • 3、使用並行回收GC策略(其實就是預設回收策略)

    新增虛擬機器引數:-Xmx10m -Xms10m -XX:+UseParallelGC -XX:+PrintGCDetails 執行結果中取出一段:

    [Full GC (Allocation Failure) 
    [PSYoungGen: 0K->0K(2560K)] 
    [ParOldGen: 5969K->5742K(7168K)]
    5969K->5742K(9728K), [Metaspace: 3515K->3515K(1056768K)], 0.0062983 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]

    可以發現:

    • PSYoungGen:新生代使用的是PS(並行回收策略)
    • ParOldGen:老年代使用的是Par(並行GC)
  • 4 、使用並行GC策略

    新增虛擬機器引數:-Xmx10m -Xms10m -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails 執行結果:

    [GC (CMS Initial Mark) [1 CMS-initial-mark: 3860K(6848K)] 5475K(9920K), 0.0001004 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    [CMS-concurrent-mark-start]
    [CMS-concurrent-mark: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    [CMS-concurrent-preclean-start]
    [CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    [GC (CMS Final Remark) [YG occupancy: 1614 K (3072 K)][Rescan (parallel) , 0.0001022 secs][weak refs processing, 0.0000110 secs][class unloading, 0.0002467 secs][scrub symbol table, 0.0004242 secs][scrub string table, 0.0000756 secs][1 CMS-remark: 3860K(6848K)] 5475K(9920K), 0.0009058 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    [CMS-concurrent-sweep-start]
    [GC (Allocation Failure) [ParNew: 1667K->2K(3072K), 0.0005861 secs] 5528K->5479K(9920K), 0.0006012 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    [GC (Allocation Failure) [ParNew: 1724K->1724K(3072K), 0.0000083 secs][
        CMS[CMS-concurrent-sweep: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
     (concurrent mode failure): 5358K->4132K(6848K), 0.0027794 secs] 7083K->4132K(9920K), [Metaspace: 3515K->3515K(1056768K)], 0.0028236 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
    
    [Full GC (Allocation Failure) [CMS: 4132K->4114K(6848K), 0.0022847 secs] 4132K->4114K(9920K), [Metaspace: 3515K->3515K(1056768K)], 0.0023052 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] 

    可以清晰的看到CMS策略的執行流程:

    • ParNew:新生代使用的是Par(並行GC策略)
    • CMS:老年代使用的是CMS(併發GC策略)

    這樣對於整個程式暫停時間會非常的短暫,適合響應速度快的程式(類似秒殺活動)。

總結:以上這些GC策略都是原始GC策略,所有的GC策略都有一個問題:都要掃描全部的子記憶體空間。