文章預習:JVM性能優化(二)垃圾回收算法詳解
一、簡介
G1垃圾收集器是在jdk1.7中正式使用的全新的垃圾收集器,oracle官方計劃在jdk9中將G1變成默認的垃圾收集器,以替代CMS
G1的設計 原則就是簡化JVM性能調優,開發人員只需要簡單的三步即可完成調優:
- 第一步:開啓G1垃圾收集器
- 第二步:設置堆的最大內存
- 第三步:設置最大的停頓時間
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):
- G1 GC在初始標記的存活區掃描對老年代的引用,並標記被引用的對象
- 該階段與應用程序(非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:
- 設置的G1區域的大小,值是2的冪,範圍是 1MB 到 32MB之間,目標是根據最小的Java堆大小劃分出約2048個區域
- 默認 是堆內存的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垃圾回收器優化建議
-
年輕代大小:
- 避免使用 -Xmn選項或 -XX:NewRatio 等其他相關選項顯示設置年輕代大小
- 固定年輕代的大小會覆蓋暫停時間目標
-
暫停時間目標不要太過嚴苛
- G1 GC 的吞吐量目標是 90% 的應用程序時間和 10%的垃圾回收時間
- 評估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表示響應時間
- Avg Pause GC Time 平均GC時間
- 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垃圾回收器就講完了,感興趣的小夥伴記得點贊關注,大家加油!