JVM性能優化(三)G1垃圾收集器

文章預習:JVM性能優化(二)垃圾回收算法詳解

一、簡介

G1垃圾收集器是在jdk1.7中正式使用的全新的垃圾收集器,oracle官方計劃在jdk9中將G1變成默認的垃圾收集器,以替代CMS

G1的設計 原則就是簡化JVM性能調優,開發人員只需要簡單的三步即可完成調優:

  1. 第一步:開啓G1垃圾收集器
  2. 第二步:設置堆的最大內存
  3. 第三步:設置最大的停頓時間

G1中提供了三種模式垃圾回收模式,Young GC、Mixed GC 和 Full GC ,在不同的條件下被觸發。

二、原理

G1垃圾收集器相比於其他收集器而言,最大的區別在於它取消了年輕代、老年代的物理劃分,取而代之的是將堆劃分爲若干個區域(Region),這些區域中包含了有邏輯上的年輕代、老年代區域
這樣做的好處就是,我們再也不用單獨的空間對每個代進行設置了,不用擔心每個代內存是否足夠。
在這裏插入圖片描述
在這裏插入圖片描述
在G1劃分區域中,年輕代垃圾收集器依然採用暫停所有應用線程的方式,將存活對象拷貝到老年代或者Survivor空間,G1收集器通過將對象從一個區域複製到另一個區域,完成了清理工作。

這就意味着,在正常的處理過程中,G1完成了堆的壓縮(至少是部分堆的壓縮),這樣也就不會有CMS內存碎片問題的存在了,

在G1中,有一種特殊的區域,叫Humongous區域。

  • 如果一個對象佔用的空間超過了分區容量50%以上,G1收集器就認爲這是一個巨型對象
  • 這些巨型對象,默認直接會被分配在老年代,但是如果它是一個短期存在的巨型對象,就會對垃圾收集器造成負面影響。
  • 爲了解決這個問題,G1劃分了一個Humongous區,它用來專門存放巨型對象,如果一個H區裝不下一個巨型對象,那麼G1會尋找連續的H分區存儲,爲了能找到連續的H區,有時候不得不啓動 Full GC

三、Young GC

Young GC主要是對Eden區進行GC,它在Eden空間耗盡時會被觸發。

  • Eden 空間的數據移動到Survivor 空間中,如果Survivor空間不夠,Eden空間的部分數據會直接晉升到年老代空間
  • Survivor區的數據移動到新的Survivor區中,也有部分數據晉升到老年代空間中
  • 最終Eden空間的數據爲空,GC停止工作,應用線程繼續執行

在這裏插入圖片描述

3.1、Remembered Set(已記憶集合)

在GC年輕代的對象時,我們如何找到年輕代中對象的根對象呢?
根對象可能是在年輕代中,也可以在老年代中,那麼老年代的所有對象都是根嗎?
如果全量掃描老年代,那麼這樣掃描下來會耗費大量的時間
於是,G1引進了Rset的概念,它的全稱是 Remembered Set,其作用是跟蹤執行某個堆內的對象引用
在這裏插入圖片描述
每個Region初始化時,會初始化一個remembered set(已記憶集合),這個翻譯有點拗口,以下簡稱RSet,該集合用來記錄並跟蹤其它Region指向該Region中對象的引用,每個Region默認按照512Kb劃分成多個Card,所以RSet需要記錄的東西應該是 xx Region的 xx Card。

3.2、Mixed GC

當越來越多的對象晉升到老年代Old Region時,爲了避免堆內存被耗盡,虛擬機會觸發一個混合的垃圾收集器,既Mixed GC,該算法並不是一個Old GC,除了回收整個Young Region,還會回收一部分的Old Region,這裏需要注意:是一部分老年代,而不是全部老年代,可以選擇那些Old region 進行收集,從而可以對垃圾回收的耗時時間進行控制,也要注意的是Mixed GC並不是Full GC
Mixed GC什麼時候出發?由參數 -XX:InitiatingHeapOccupancyPercent=n決定。默認:45%,該參數的意思是:當老年代大小佔用整個堆大小百分比達到該閾值時觸發。

它的GC步驟分兩步:
1. 全局併發標記(global concurrent marking)
2. 拷貝存活對象(evacuation)

3.2.1 全局併發標記

全局併發標記,執行過程分爲五個步驟:

  • **初始標記(initial mark ,STW):**標記從根節點直接可達的對象,這個階段會執行一次年輕代GC,會產生全局停頓。

  • 根區域掃描(root region scan):

    1. G1 GC在初始標記的存活區掃描對老年代的引用,並標記被引用的對象
    2. 該階段與應用程序(非STW)同時運行,並且只有完成該階段後,才能開始下一次STW年輕代垃圾回收。
  • 併發標記(Concurrent Marking): G1 GC在整個堆中查找可訪問的(存活的)對象,該階段與應用程序同時運行,可以被STW年輕代垃圾回收中斷

  • 重新標記(Remark,STW): 該階段是STW回收,因爲程序在運行,針對上一次的標記進行修改。

  • 清楚垃圾(Cleanup,STW): 輕點和重置標記狀態,該階段會STW,這個階段並不會實際上去做垃圾的收集,等待evacuation階段來回收

3.2.2 拷貝存活對象

Evacuation 階段是全暫停的,該階段把一部分Region裏的活對象拷貝到另一部分Region中,從而實現垃圾的回收清理。

四、G1 收集器相關參數

  • -XX:+UseG1GC: 使用G1垃圾收集器

  • -XX:MaxGCPauseMillis: 設置期望達到最大GC停頓時間指標(JVM會盡力實現,但不保證達到),默認值是200毫秒

  • -XX:G1HeapRegionSize=n:

    1. 設置的G1區域的大小,值是2的冪,範圍是 1MB 到 32MB之間,目標是根據最小的Java堆大小劃分出約2048個區域
    2. 默認 是堆內存的1/2000
  • -XX:ParallelGCThreads=n: 設置STW 工作線程數的值,將 n的值設置爲邏輯處理器的數量,n的值與邏輯處理器的數量相同,最多爲8

  • -XX:ConcGCThreads=n: 設置並行標記的線程數,將n設置爲並行垃圾回收線程數(ParallelGCThreads)的1/4左右

  • **-XX:InitiatingHeapOccupancyPercent=n: **設置出發標記週期的java堆佔用率閾值,默認佔用率是這個Java堆的45%

五、測試

5.1 測試代碼:

public class TestGC {

    // 實現:不斷的產生新的數據(對象),隨機的去廢棄對象(垃圾)
    public static void main(String[] args) throws Exception {
        List<Object> list = new ArrayList<>();
        while (true){
            int sleep = new Random().nextInt(100);
            if(System.currentTimeMillis() % 2 ==0){
                //當前的時間戳,是偶數
                list.clear();
            }else{
                //向List中添加1000個對象
                for (int i = 0; i < 10000; i++) {
                    Properties properties = new Properties();
                    properties.put("key_"+i,"value_"+System.currentTimeMillis()+i);
                    list.add(properties);
                }
            }
            Thread.sleep(sleep);
        }
    }
}

5.2 測試參數:

-XX:+UseG1GC -XX:MaxGCPauseMillis=100 -XX:+PrintGCDetails -Xmx256m

在這裏插入圖片描述

5.3 日誌輸出:

[GC pause (G1 Evacuation Pause) (young), 0.0027884 secs]
   [Parallel Time: 2.2 ms, GC Workers: 8]
      [GC Worker Start (ms): Min: 542.1, Avg: 542.1, Max: 542.2, Diff: 0.1]
      # 掃描根節點
      [Ext Root Scanning (ms): Min: 0.1, Avg: 0.3, Max: 0.5, Diff: 0.4, Sum: 2.1]
      # 更新RS區域所消耗的時間
      [Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
         [Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0]
      [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]
      # 對象拷貝
      [Object Copy (ms): Min: 1.6, Avg: 1.8, Max: 1.9, Diff: 0.4, Sum: 14.6]
      [Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.3]
         [Termination Attempts: Min: 1, Avg: 5.5, Max: 7, Diff: 6, Sum: 44]
      [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.2]
      [GC Worker Total (ms): Min: 2.1, Avg: 2.1, Max: 2.2, Diff: 0.1, Sum: 17.2]
      [GC Worker End (ms): Min: 544.2, Avg: 544.2, Max: 544.2, Diff: 0.0]
   [Code Root Fixup: 0.0 ms]
   [Code Root Purge: 0.0 ms]
   [Clear CT: 0.1 ms] # 清空CardTable
   [Other: 0.5 ms]
      [Choose CSet: 0.0 ms] # 選取 CSet
      [Ref Proc: 0.4 ms] # 弱引用、軟引用的處理耗時
      [Ref Enq: 0.0 ms]	# 弱引用、軟引用的入隊耗時
      [Redirty Cards: 0.0 ms]
      [Humongous Register: 0.0 ms] # 大對象區域註冊耗時
      [Humongous Reclaim: 0.0 ms]  # 大對象區域回收耗時
      [Free CSet: 0.0 ms]
   [Eden: 12.0M(12.0M)->0.0B(13.0M) Survivors: 0.0B->2048.0K Heap: 12.0M(252.0M)->3282.5K(252.0M)] # 年輕代的大小統計
 [Times: user=0.00 sys=0.00, real=0.00 secs] 

五、對於G1垃圾回收器優化建議

  • 年輕代大小:

    1. 避免使用 -Xmn選項或 -XX:NewRatio 等其他相關選項顯示設置年輕代大小
    2. 固定年輕代的大小會覆蓋暫停時間目標
  • 暫停時間目標不要太過嚴苛

    1. G1 GC 的吞吐量目標是 90% 的應用程序時間和 10%的垃圾回收時間
    2. 評估G1 GC的吞吐量時,暫停時間目標不要太嚴苛,目標太過嚴苛表示你願意承受更多的垃圾回收開銷,而這會直接影響到吞吐量

六、可視化GC日誌分析工具

6.1 GC日誌輸出參數

前面通過 -XX:PrintGCDetails可以對GC日誌進行打印,我們就可以在控制檯查看,這樣雖然可以查看GC的信息,但是並不直觀,可以藉助於第三方的GC日誌分析工具進行查看

在日誌打印輸出設計到的參數如下:

  • -XX:+PrintGC:輸出GC日誌
  • -XX:+PrintGCDetails:輸出GC的詳細日誌
  • -XX:+PrintGCTimeStamps:輸出GC的時間戳(以基準的時間的形式)
  • -XX:+PrintGCDateStamps:輸出GC的時間戳(以日期的形式,如: 2020-05-04T21:25.234+0800)
  • -XX:+PrintHeapAtGC:在進行GC的前後打印出堆的信息
  • -Xloggc:…/logs/gc.log 日誌文件的輸出路徑

測試參數:

-XX:+UseG1GC -XX:MaxGCPauseMillis=100 -Xmx256m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:E://test//gc.log

運行main 方法後,我們就可以在 E://test//gc.log下發現一個 gc.log 的日誌文件:
在這裏插入圖片描述

6.2 GC Easy 可視化工具

GC Easy是一款在線可視化工具,易用、功能強大,可以通過GC日誌分析進行內存泄露檢測、GC暫停原因分析、JVM配置建議優化等功能,而且是可以免費使用的
網站地址:http://gceasy.io
在這裏插入圖片描述
上傳gc.log後,點擊Analyze,就可以查看日誌報告了

JVM的各個分代區域分配的內存及使用峯值的內存
在這裏插入圖片描述

關鍵性能指標: 吞吐量及GC暫停平均時間、最大時間、各個時間段的比例

  • Throughput表示的是吞吐量
  • Latency表示響應時間
    1. Avg Pause GC Time 平均GC時間
    2. Max Pause GC TIme 最大GC時間
      在這裏插入圖片描述

第一部分是Heap after GC,GC後堆的內存圖,堆是用來存儲對象的
在這裏插入圖片描述

第二部分是Heap before GC,這是GC前堆的使用率,可以看出隨着程序的運行,堆使用率越來越高,堆被對象佔用的內存越來越大。
在這裏插入圖片描述
第三部分是GC Duration Time,就是GC持續時間。從圖中可以看到,發生Full GC的時間持續的比較短,而Young GC持續的時間比較長。圖中橫座標表示GC發生的時間段,縱座標表示的是GC持續時間。

在這裏插入圖片描述
表示的是GC回收掉的垃圾對象的內存大小。在這裏插入圖片描述
GC Statistics
在這裏插入圖片描述
**Reclaimed Bytes(gb):**表示的是堆內存中Minor GC和Full GC回收垃圾對象的內存。
GC cumulative Time(ms):總計GC時間,包括Minor GC和Full GC,時間單位爲ms。
GC Average Time(ms):GC平均時間,包括了Minor GC和Full GC。

在這裏插入圖片描述
總GC統計
MinorGC的統計
FullGC的統計
GC暫停程序的統計

七、結束語

到這裏G1垃圾回收器就講完了,感興趣的小夥伴記得點贊關注,大家加油!

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