談JVM參數GC線程數ParallelGCThreads合理性設置

語言: CN / TW / HK

作者:京東零售 劉樂

1. ParallelGCThreads參數含義

在講這個參數之前,先談談JVM垃圾回收(GC)算法的兩個優化標的:吞吐量和停頓時長。JVM會使用特定的GC收集線程,當GC開始的時候,GC線程會和業務線程搶佔CPU時間,吞吐量定義為CPU用於業務線程的時間與CPU總消耗時間的比值。為了承接更大的流量,吞吐量越大越好。

為了安全的垃圾回收,在GC或者GC某個階段,所有業務線程都會被暫停,也就是STW(Stop The World),STW持續時間就是停頓時長,停頓時長影響響應速度,因此越小越好。

這兩個優化目標是有衝突的,在一定範圍內,參與GC的線程數越多,停頓時長越小,但吞吐量也越小。生產實踐中,需要根據業務特點設置一個合理的GC線程數,取得吞吐量和停頓時長的平衡。

目前廣泛使用的GC算法,包括PS MarkSweep/PS Scavenge, ConcurrentMarkSweep/ParNew, G1等,都可以通過ParallelGCThreads參數來指定JVM在並行GC時參與垃圾收集的線程數。該值設置過小,GC暫停時間變長影響RT,設置過大則影響吞吐量,從而導致CPU過高。

2. ParallelGCThreads參數設置

GC併發線程數可以通過JVM啟動參數: -XX:ParallelGCThreads=<N>來指定。在未明確指定的情況下,JVM會根據邏輯核數ncpus,採用以下公式來計算默認值:

◦當ncpus小於8時,ParallelGCThreads = ncpus

◦否則 ParallelGCThreads = 8 + (ncpus - 8 ) ( 5/8 )

一般來説,在無特殊要求下,ParallelGCThreads參數使用默認值就可以了。但是在JRE版本1.8.0_131之前,JVM無法感知Docker的CPU限制,會使用宿主機的邏輯核數計算默認值。 比如部署在128核物理機上的容器,JVM中默認ParallelGCThreads為83,遠超過了容器的核數。過多的GC線程數搶佔了業務線程的CPU時間,加上線程切換的開銷,較大的降低了吞吐量。因此JRE 1.8.0_131之前的版本,未明確指定ParallelGCThreads會有較大的風險。

3. ParallelGCThreads參數實驗

創建 8C12G 容器,宿主機是128C。模擬線上真實流量,採用相同QPS,觀察及對比JVM YoungGC,JVM CPU,容器CPU等監控數據。場景如下:

◦場景1: JVM ParallelGCThreads 默認值,QPS = 420,持續5分鐘,CPU恆定在70%

◦場景2: JVM ParallelGCThreads=8,QPS = 420,持續5分鐘,CPU恆定在65%

◦場景3: JVM ParallelGCThreads 默認值,QPS瞬時發壓到420,前1min CPU持續100%

◦場景4: JVM ParallelGCThreads=8,QPS瞬時發壓到420,前2s CPU持續100%,後面回落

從監控數據來看,各場景下CPU差距較明顯,特別是場景3和場景4的對比。場景3由於GC線程過多,CPU持續100%時長達1分鐘。可以得出以下兩個結論:

1.修改 ParallelGCThreads = 8後,同等QPS情況下,CPU會降低5%左右

2.修改 ParallelGCThreads = 8後,瞬間發壓且CPU打滿情況下,CPU恢復較快

3-9.png

10.png

圖1: 容器CPU對比圖:場景3(上)和場景4(下)

11.png 12.png 圖2: JVM Young GC對比圖:場景3(上)和場景4(下)

4. ParallelGCThreads修改建議

ParallelGCThreads配置存在風險的應用,修改方式為以下兩種方案(任選一種):

◦升級JRE版本到1.8.0_131以上,推薦1.8.0_192

◦在JVM啟動參數明確指定 -XX:ParallelGCThreads=<N>,N為下表的推薦值:

容器核數 2 4 8 16 32 64
推薦值 2 4 8 13 23 43
建議上下界 1~2 2~4 4~8 8~16 16~32 32~64