JVM小冊(1)------jstat和Parallel GC日志
一. 背景
在生産環境中,有時候會遇到OOM的情況,抛開
Arthas
等比較成熟的工具以外,我們可以使用
java
提供的
jatat
和
jps
、
jmap
等工具來幫助我們排查問題和定位原因,本系列小冊先簡單記錄一下實際使用方式,後續對
JVM
進行分析.
二. 場景舉例
比如目前有一台伺服器,部署了一個java服務,我們為它配置設定的堆記憶體如下:
#最大堆記憶體4G,最小2G, 一般來說建議這2個參數設定成一樣的,減少因為GC以後動态記憶體擴容對系統造成的開銷
-Xmx4096M -Xms2048M
那麼怎麼來檢視這個服務在運作過程中的GC情況和JVM中記憶體的使用情況呢?
1. 添加JVM參數
首先我們要在jar包啟動的時候添加
列印GC日志
的參數,這裡參數如下:
#列印GC詳細資訊
-XX:+PrintGCDetails
#列印GC時間
-XX:+PrintGCTimeStamps
#輸出GC日志
-Xloggc:gc.log
添加完以上三個參數以後,在jar啟動後,會在你
-Xloggc
參數指定位置生成對應的gc日志,如下:
日志的内容我們等下再詳細講解.接下來比如我們以上配置的java服務發送了OOM,或者你監控發現該服務的記憶體占用持續走高, 這個時候我們可能會去排查原因.
2. 使用 jps
指令檢視java程序的PID
jps
在該服務部署的伺服器上,運作指令
jps
,你可能會得到以下資訊:
6 xxx.jar
366 Jps
上面的傳回結果中,第一行表示目前運作的一個java程序,前面的數字是該程序的
PID
,下面一行是Jps指令本身,不用管.
3. 使用 jstat
指令檢視JVM記憶體活動
jstat
根據第二步查到的該服務的PID是6, 我們就可以用
jstat
指令去檢視記憶體活動,指令如下:
/opt/app # jstat -gc 6 1000 100
指令解釋: 間隔1秒列印100次PID為6的java程序的gc情況
列印結果如下:
其實
jstat
指令除了
-gc
參數外,還有很多可選,這裡先不做擴充.現在我們得到的結果要怎麼看呢?
4. jstat結果解釋
以上的圖檔中,每一個名額的解釋如下:
S0C From Survivor區大小(位元組)
S1C To Survivor區大小(位元組)
S0U From Survivor區目前使用的記憶體大小(位元組)
S1U To Survivor區目前使用的記憶體大小(位元組)
EC Eden區的大小(位元組)
EU Eden區目前使用大小(位元組)
OC 老年代大小(位元組)
OU 老年代目前使用大小(位元組)
MC 方法區(永久代,中繼資料區)大小(位元組)
MU 方法區目前使用的大小(位元組)
CCSC 目前壓縮類空間的容量
CCSU 目前壓縮類空間也是用的容量
YGC 系統運作迄今為止的YoungGC次數
YGCT YoungGC耗時
FGC 系統運作迄今為止的Full GC次數
FGCT FullGC耗時
GCT 所有GC總耗時
5. 根據GC情況分析
根據
jstat
檢視出來的gc情況,我們可能需要得到以下幾個名額:
新生代對象增長的速率:
YoungGC的觸發頻率:
YoungGC的耗時:
每次YoungGC後有多少對象存活下來:
每次YoingGC後有多少對象進入了老年代:
老年代對象增長的速率:
FullGC觸發頻率:
FullGC的耗時:
根據以上參數,再加上對JVM記憶體模型的認識,我們可以清楚的知道目前jvm程序的運作情況,有助于我們對問題的定位.
6. GC日志分析
- 對于已經啟動的jvm程序,我們可以使用如下指令檢視目前jvm使用的參數,版本,以及垃圾回收器等資訊:
java -XX:+PrintCommandLineFlags -version
- 也可以使用如下指令檢視目前jvmGC的詳細資訊
java -XX:+PrintGCDetails -version
1) Parallel Scavenge + Serial Old GC日志分析
接着回過頭來說gc日志,我們之前在jar啟動參數中加入了列印gc資訊的參數.在jvm啟動以後,會在我們指定的目錄列印gc日志,我們打開剛才那個應用的gc日志,你可能會看到以下資訊:
OpenJDK 64-Bit Server VM (25.212-b04) for linux-amd64 JRE (1.8.0_212-b04), built on May 4 2019 17:56:12 by "buildozer" with gcc 8.3.0
Memory: 4k page, physical 5120000k(5118348k free), swap 0k(0k free)
CommandLine flags: -XX:InitialHeapSize=2147483648 -XX:MaxHeapSize=4294967296 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC
4.737: [GC (Metadata GC Threshold) [PSYoungGen: 367429K->23240K(611840K)] 367429K->23256K(2010112K), 0.0300742 secs] [Times: user=0.04 sys=0.01, real=0.03 secs]
4.768: [Full GC (Metadata GC Threshold) [PSYoungGen: 23240K->0K(611840K)] [ParOldGen: 16K->22304K(1398272K)] 23256K->22304K(2010112K), [Metaspace: 20775K->20775K(1067008K)], 0.0600170 secs] [Times: user=0.09 sys=0.00, real=0.06 secs]
6.329: [GC (Allocation Failure) [PSYoungGen: 524800K->8127K(611840K)] 547104K->30439K(2010112K), 0.0197683 secs] [Times: user=0.04 sys=0.00, real=0.02 secs]
7.776: [GC (Allocation Failure) [PSYoungGen: 532927K->18132K(611840K)] 555239K->40444K(2010112K), 0.0574908 secs] [Times: user=0.04 sys=0.00, real=0.06 secs]
8.528: [GC (Allocation Failure) [PSYoungGen: 542932K->16422K(928256K)] 565244K->38742K(2326528K), 0.0376001 secs] [Times: user=0.09 sys=0.00, real=0.03 secs]
8.755: [GC (GCLocker Initiated GC) [PSYoungGen: 184691K->9923K(928256K)] 207011K->32243K(2326528K), 0.0178837 secs] [Times: user=0.04 sys=0.01, real=0.02 secs]
8.859: [GC (Metadata GC Threshold) [PSYoungGen: 91421K->8481K(1247232K)] 113741K->30801K(2645504K), 0.0144768 secs] [Times: user=0.03 sys=0.01, real=0.01 secs]
8.873: [Full GC (Metadata GC Threshold) [PSYoungGen: 8481K->0K(1247232K)] [ParOldGen: 22320K->16585K(1398272K)] 30801K->16585K(2645504K), [Metaspace: 34975K->34975K(1081344K)], 0.1082484 secs] [Times: user=0.20 sys=0.01, real=0.11 secs]
10.761: [GC (Allocation Failure) [PSYoungGen: 1223168K->15163K(1247744K)] 1239753K->31748K(2646016K), 0.0446415 secs] [Times: user=0.05 sys=0.02, real=0.04 secs]
13.097: [GC (Allocation Failure) [PSYoungGen: 1238331K->23551K(1343488K)] 1254916K->68603K(2741760K), 0.1209356 secs] [Times: user=0.14 sys=0.02, real=0.13 secs]
15.171: [GC (Allocation Failure) [PSYoungGen: 1343487K->38905K(1358848K)] 1388539K->99859K(2757120K), 0.1189547 secs] [Times: user=0.15 sys=0.02, real=0.12 secs]
17.227: [GC (Allocation Failure) [PSYoungGen: 1358841K->23895K(1339392K)] 1419795K->104346K(2737664K), 0.0751066 secs] [Times: user=0.11 sys=0.01, real=0.07 secs]
18.043: [GC (Metadata GC Threshold) [PSYoungGen: 414691K->7705K(1349120K)] 495143K->100326K(2747392K), 0.0331763 secs] [Times: user=0.05 sys=0.00, real=0.03 secs]
18.077: [Full GC (Metadata GC Threshold) [PSYoungGen: 7705K->0K(1349120K)] [ParOldGen: 92620K->56466K(1398272K)] 100326K->56466K(2747392K), [Metaspace: 57545K->57545K(1101824K)], 0.2955924 secs] [Times: user=0.52 sys=0.01, real=0.30 secs]
21.465: [GC (Allocation Failure) [PSYoungGen: 1300480K->27367K(1348096K)] 1356946K->83834K(2746368K), 0.0575351 secs] [Times: user=0.09 sys=0.00, real=0.06 secs]
30.452: [Full GC (Metadata GC Threshold) [PSYoungGen: 15506K->0K(1351168K)] [ParOldGen: 90168K->76729K(1721856K)] 105674K->76729K(3073024K), [Metaspace: 95611K->95607K(1136640K)], 0.3909874 secs] [Times: user=0.69 sys=0.01, real=0.39 secs]
94.216: [GC (Allocation Failure) [PSYoungGen: 1307648K->23645K(1350656K)] 1384377K->100382K(3072512K), 0.0587363 secs] [Times: user=0.14 sys=0.00, real=0.06 secs]
對于以上日志,我們應該怎麼分析檢視呢?
這裡我們為了排除幹擾,我手動将日志進行了換行, 并且,一些同類型的日志我都删掉了,這樣做的原因是我們先一起來看下這個日志的構成以及如何檢視.知道如何檢視以後,可以再根據日志的時間線來分析日志.
那麼上面的日志我簡化成如下部分:
可以看到,我簡化後,将日志劃分成了14句,下面我們依次來看:
1
OpenJDK 64-Bit Server VM (25.212-b04) for linux-amd64 JRE (1.8.0_212-b04), built on May 4 2019 17:56:12 by "buildozer" with gcc 8.3.0
Memory: 4k page, physical 5120000k(5118348k free), swap 0k(0k free)
這一部分主要是表明目前java虛拟機的版本、JDK的版本還有伺服器的系統核心等參數.
2
CommandLine flags: -XX:InitialHeapSize=2147483648 -XX:MaxHeapSize=4294967296 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC
這一句日志中表明目前
JVM
采用的參數是哪些.如果不設定jvm參數, 也可以通過這部分日志來得出目前jvm預設的參數.可以看到目前我們使用的垃圾回收器是
Parallel
, 這也是1.8預設的垃圾回收器.
3
4.737: [GC (Metadata GC Threshold) [PSYoungGen: 367429K->23240K(611840K)] 367429K->23256K(2010112K), 0.0300742 secs] [Times: user=0.04 sys=0.01, real=0.03 secs]
-
表示GC發生的時間,這裡顯示的是秒級别的時間,如果你再jvm啟動的時候加入了參數4.737:
它也會列印出目前GC的具體日期.-XX:+PrintGCDateStamps
-
這裡表示目前發生GC的原因,在這裡是由于[GC (Metadata GC Threshold)
Metadata
區導緻的GC. 但是在這裡我很疑惑,1.8中,jvm中的永久代便用Metadata元空間來取代了,而元空間的記憶體使用的是堆外記憶體,既然如此,為什麼元空間會不足呢?
查閱資料後發現,在預設情況下,元空間大小的預設值是
,也就是大約20M左右.21810376B
檢視目前jvm初始化參數指令: java -XX:+PrintFlagsInitial
如果要自定義元空間的大小,可以使用參數
來設定.-XX:MetaspaceSize=128M
-
這一句的意思是,Young去發生了GC,在GC前Young區是[PSYoungGen: 367429K->23240K(611840K)]
,在GC後是367429k
,23240k
辨別Yuong區總大小.(611840k)
-
這句的意思是,367429K->23256K(2010112K)
堆記憶體在GC前的大小-->GC後的大小(堆記憶體總大小)
-
這裡表示本次GC的持續時間是0.0300742秒0.0300742 secs
-
這裡是本次GC的詳細的時間資訊[Times: user=0.04 sys=0.01, real=0.03 secs]
- real: 指的是在此次GC中所花費的總時間
- user: 指的是CPU工作在使用者态所花費的時間
- sys: 指的是CPU工作在核心态所花費的時間
***
的文章
在這裡,
+user
是CPU花費的實際時間,這個值統計了所有CPU上的時間,如果程序在多線程的環境下,這個值是會超出sys
的值的,也就是real
+user
>=sys
存在多線程的原因在于,在回收過程中,存在并發的GC算法,比如real
和ParNew
.ParallelOld
4
4.768: [Full GC (Metadata GC Threshold) [PSYoungGen: 23240K->0K(611840K)] [ParOldGen: 16K->22304K(1398272K)] 23256K->22304K(2010112K), [Metaspace: 20775K->20775K(1067008K)], 0.0600170 secs] [Times: user=0.09 sys=0.00, real=0.06 secs]
這部分日志中,我們隻對上面沒有提及的部分做說明.
-
表示目前發送的是Full GC (Metadata GC Threshold)
Full GC
-
表示老年代Old區發送的垃圾回收,回收前的大小 -> 回收後的大小(Old區總大小)[ParOldGen: 16K->22304K(1398272K)]
-
表示元空間區域發送的垃圾回收, 回收前是[Metaspace: 20775K->20775K(1067008K)]
,回收後是20775k
,總大小是20775k
1067008k
基本上,到這裡的話,Parallel的GC日志我們已經分析完了.下面開始CMS和ParNew垃圾回收器産生的GC日志分析
2) ParNew + CMS GC日志分析
JDK1.8預設使用的垃圾回收器是
Parallel
,該垃圾回收在進行GC時是單線程的,是以建議在生産環境中使用
ParNew
+
CMS
垃圾回收器.
-XX:+UseConcMarkSweepGC
使用以上JVM參數即可指定jvm的垃圾回收器為: 新生代
ParNew
老年代
CMS
,一般來說,我們會設定新生代的大小,以為預設情況下,有可能jvm預設的新生代很小,如果堆記憶體給了比較大的空間,新生代不做調整的話,浪費記憶體空間.
以下為我使用的jvm參數:
#使用ParNew+CMS垃圾回收器
-XX:+UseConcMarkSweepGC
#列印GC詳情
-XX:+PrintGCDetails
#列印GC的日期
-XX:+PrintGCDateStamps
#列印GC時間
-XX:+PrintGCTimeStamps
#指定GC日志位置
-Xloggc:/opt/app/logs/gc.log
#指定最大堆記憶體
-Xmx2048M
#指定最小堆記憶體
-Xms2048M
#新生代和老年代的記憶體占比1:2
-XX:NewRatio=2
#Eden區和Survivor比例8:1
-XX:SurvivorRatio=8
#指定新生代的最小記憶體空間
-XX:NewSize=715784192
#指定新生代的最大記憶體空間
-XX:MaxNewSize=715784192
使用以上JVM參數以後,我們檢視gc日志如下(可以使用
less 檔案名
的方式檢視gc日志,PageUp向上翻頁,PageDown向下翻頁):
OpenJDK 64-Bit Server VM (25.212-b04) for linux-amd64 JRE (1.8.0_212-b04), built on May 4 2019 17:56:12 by "buildozer" with gcc 8.3.0
Memory: 4k page, physical 3072000k(3070320k free), swap 0k(0k free)
CommandLine flags: -XX:InitialHeapSize=2147483648 -XX:MaxHeapSize=2147483648 -XX:MaxNewSize=715784192 -XX:NewRatio=2 -XX:NewSize=715784192 -XX:OldPLABSize=16 -XX:+PrintGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:Survivor
Ratio=8 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
2021-04-29T16:37:58.684+0800: 5.799: [GC (Allocation Failure) 2021-04-29T16:37:58.684+0800: 5.799: [ParNew: 559232K->26278K(629120K), 0.0439056 secs] 559232K->26278K(2027264K), 0.0440200 secs] [Times: user=0.06 sys=0.01, real=0.04 secs]
2021-04-29T16:37:58.728+0800: 5.843: [GC (CMS Initial Mark) [1 CMS-initial-mark: 0K(1398144K)] 41154K(2027264K), 0.0076770 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
2021-04-29T16:37:58.736+0800: 5.851: [CMS-concurrent-mark-start]
2021-04-29T16:37:58.744+0800: 5.859: [CMS-concurrent-mark: 0.008/0.008 secs] [Times: user=0.05 sys=0.00, real=0.01 secs]
2021-04-29T16:37:58.744+0800: 5.859: [CMS-concurrent-preclean-start]
2021-04-29T16:37:58.747+0800: 5.862: [CMS-concurrent-preclean: 0.003/0.003 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
2021-04-29T16:37:58.748+0800: 5.862: [CMS-concurrent-abortable-preclean-start]
2021-04-29T16:37:59.594+0800: 6.708: [CMS-concurrent-abortable-preclean: 0.458/0.846 secs] [Times: user=1.59 sys=0.08, real=0.85 secs]
2021-04-29T16:37:59.594+0800: 6.708: [GC (CMS Final Remark) [YG occupancy: 394027 K (629120 K)]2021-04-29T16:37:59.594+0800: 6.709: [Rescan (parallel) , 0.1164667 secs]2021-04-29T16:37:59.710+0800: 6.825: [weak refs processing, 0.0000352 secs]2021-0
4-29T16:37:59.710+0800: 6.825: [class unloading, 0.0041990 secs]2021-04-29T16:37:59.715+0800: 6.829: [scrub symbol table, 0.0038982 secs]2021-04-29T16:37:59.719+0800: 6.833: [scrub string table, 0.0005058 secs][1 CMS-remark: 0K(1398144K)] 394027K(20
27264K), 0.1260089 secs] [Times: user=0.29 sys=0.00, real=0.12 secs]
2021-04-29T16:37:59.720+0800: 6.835: [CMS-concurrent-sweep-start]
2021-04-29T16:37:59.720+0800: 6.835: [CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2021-04-29T16:37:59.720+0800: 6.835: [CMS-concurrent-reset-start]
2021-04-29T16:37:59.791+0800: 6.906: [CMS-concurrent-reset: 0.071/0.071 secs] [Times: user=0.07 sys=0.05, real=0.07 secs]
2021-04-29T16:38:00.341+0800: 7.455: [GC (Allocation Failure) 2021-04-29T16:38:00.341+0800: 7.455: [ParNew: 585510K->23331K(629120K), 0.0716625 secs] 585510K->23331K(2027264K), 0.0717529 secs] [Times: user=0.14 sys=0.02, real=0.07 secs]
2021-04-29T16:38:01.715+0800: 8.829: [GC (Allocation Failure) 2021-04-29T16:38:01.715+0800: 8.829: [ParNew: 582563K->36089K(629120K), 0.0958375 secs] 582563K->36089K(2027264K), 0.0959258 secs] [Times: user=0.14 sys=0.00, real=0.09 secs]
2021-04-29T16:38:02.687+0800: 9.801: [GC (Allocation Failure) 2021-04-29T16:38:02.687+0800: 9.801: [ParNew: 595321K->29885K(629120K), 0.0493737 secs] 595321K->29885K(2027264K), 0.0494595 secs] [Times: user=0.12 sys=0.01, real=0.05 secs]
2021-04-29T16:38:03.564+0800: 10.679: [GC (Allocation Failure) 2021-04-29T16:38:03.564+0800: 10.679: [ParNew: 589117K->38018K(629120K), 0.0469700 secs] 589117K->38018K(2027264K), 0.0470567 secs] [Times: user=0.11 sys=0.00, real=0.04 secs]
2021-04-29T16:38:04.721+0800: 11.835: [GC (Allocation Failure) 2021-04-29T16:38:04.721+0800: 11.835: [ParNew: 597250K->38175K(629120K), 0.1040785 secs] 597250K->38175K(2027264K), 0.1041795 secs] [Times: user=0.10 sys=0.01, real=0.11 secs]
2021-04-29T16:38:04.825+0800: 11.940: [GC (CMS Initial Mark) [1 CMS-initial-mark: 0K(1398144K)] 39294K(2027264K), 0.0224121 secs] [Times: user=0.04 sys=0.00, real=0.02 secs]
2021-04-29T16:38:04.848+0800: 11.962: [CMS-concurrent-mark-start]
2021-04-29T16:38:04.856+0800: 11.970: [CMS-concurrent-mark: 0.008/0.008 secs] [Times: user=0.05 sys=0.00, real=0.01 secs]
2021-04-29T16:38:04.856+0800: 11.971: [CMS-concurrent-preclean-start]
2021-04-29T16:38:04.860+0800: 11.974: [CMS-concurrent-preclean: 0.004/0.004 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
2021-04-29T16:38:04.860+0800: 11.975: [CMS-concurrent-abortable-preclean-start]
2021-04-29T16:38:05.711+0800: 12.826: [CMS-concurrent-abortable-preclean: 0.779/0.851 secs] [Times: user=1.65 sys=0.07, real=0.85 secs]
2021-04-29T16:38:05.711+0800: 12.826: [GC (CMS Final Remark) [YG occupancy: 331082 K (629120 K)]2021-04-29T16:38:05.711+0800: 12.826: [Rescan (parallel) , 0.0991187 secs]2021-04-29T16:38:05.810+0800: 12.925: [weak refs processing, 0.0000489 secs]202
1-04-29T16:38:05.810+0800: 12.925: [class unloading, 0.0068616 secs]2021-04-29T16:38:05.817+0800: 12.932: [scrub symbol table, 0.0083112 secs]2021-04-29T16:38:05.826+0800: 12.940: [scrub string table, 0.0006307 secs][1 CMS-remark: 0K(1398144K)] 3310
82K(2027264K), 0.1164628 secs] [Times: user=0.19 sys=0.00, real=0.12 secs]
2021-04-29T16:38:05.828+0800: 12.942: [CMS-concurrent-sweep-start]
2021-04-29T16:38:05.828+0800: 12.942: [CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2021-04-29T16:38:05.828+0800: 12.942: [CMS-concurrent-reset-start]
2021-04-29T16:38:05.831+0800: 12.946: [CMS-concurrent-reset: 0.003/0.003 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
2021-04-29T16:38:06.290+0800: 13.405: [GC (Allocation Failure) 2021-04-29T16:38:06.290+0800: 13.405: [ParNew: 597407K->58488K(629120K), 0.1068891 secs] 597407K->58488K(2027264K), 0.1069796 secs] [Times: user=0.14 sys=0.01, real=0.11 secs]
2021-04-29T16:38:07.295+0800: 14.409: [GC (Allocation Failure) 2021-04-29T16:38:07.295+0800: 14.409: [ParNew: 617720K->53644K(629120K), 0.1625714 secs] 617720K->68440K(2027264K), 0.1626508 secs] [Times: user=0.24 sys=0.02, real=0.16 secs]
2021-04-29T16:38:08.389+0800: 15.504: [GC (Allocation Failure) 2021-04-29T16:38:08.389+0800: 15.504: [ParNew: 612876K->35515K(629120K), 0.0610254 secs] 627672K->50311K(2027264K), 0.0611143 secs] [Times: user=0.16 sys=0.01, real=0.06 secs]
2021-04-29T16:38:09.449+0800: 16.563: [GC (Allocation Failure) 2021-04-29T16:38:09.449+0800: 16.564: [ParNew: 594747K->37068K(629120K), 0.0614502 secs] 609543K->51864K(2027264K), 0.0615418 secs] [Times: user=0.09 sys=0.00, real=0.06 secs]
2021-04-29T16:38:10.525+0800: 17.639: [GC (Allocation Failure) 2021-04-29T16:38:10.525+0800: 17.639: [ParNew: 596300K->37786K(629120K), 0.0698999 secs] 611096K->52582K(2027264K), 0.0699826 secs] [Times: user=0.12 sys=0.00, real=0.07 secs]
2021-04-29T16:38:11.173+0800: 18.288: [GC (Allocation Failure) 2021-04-29T16:38:11.173+0800: 18.288: [ParNew: 597018K->36129K(629120K), 0.0386147 secs] 611814K->50925K(2027264K), 0.0386905 secs] [Times: user=0.07 sys=0.00, real=0.04 secs]
2021-04-29T16:38:12.205+0800: 19.320: [GC (Allocation Failure) 2021-04-29T16:38:12.205+0800: 19.320: [ParNew: 595361K->42277K(629120K), 0.0603755 secs] 610157K->57073K(2027264K), 0.0604587 secs] [Times: user=0.10 sys=0.00, real=0.06 secs]
2021-04-29T16:38:13.308+0800: 20.423: [GC (Allocation Failure) 2021-04-29T16:38:13.308+0800: 20.423: [ParNew: 601509K->48883K(629120K), 0.0707038 secs] 616305K->63679K(2027264K), 0.0707979 secs] [Times: user=0.12 sys=0.00, real=0.07 secs]
2021-04-29T16:38:14.230+0800: 21.344: [GC (Allocation Failure) 2021-04-29T16:38:14.230+0800: 21.344: [ParNew: 608115K->47267K(629120K), 0.0806798 secs] 622911K->68980K(2027264K), 0.0807631 secs] [Times: user=0.11 sys=0.01, real=0.08 secs]
可以看到,在加入了以上的JVM參數以後,每次GC的時間都列印的很詳細,我們可以清楚的知道每一次gc的情況.下面對這段日志進行分析.
1
OpenJDK 64-Bit Server VM (25.212-b04) for linux-amd64 JRE (1.8.0_212-b04), built on May 4 2019 17:56:12 by "buildozer" with gcc 8.3.0
Memory: 4k page, physical 3072000k(3070320k free), swap 0k(0k free)
CommandLine flags: -XX:InitialHeapSize=2147483648 -XX:MaxHeapSize=2147483648 -XX:MaxNewSize=715784192 -XX:NewRatio=2 -XX:NewSize=715784192 -XX:OldPLABSize=16 -XX:+PrintGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:Survivor
Ratio=8 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
這一段依舊是說明目前JVM使用的一些參數,還有版本什麼的,不再多說
2
2021-04-29T16:37:58.684+0800: 5.799: [GC (Allocation Failure) 2021-04-29T16:37:58.684+0800: 5.799: [ParNew: 559232K->26278K(629120K), 0.0439056 secs] 559232K->26278K(2027264K), 0.0440200 secs] [Times: user=0.06 sys=0.01, real=0.04 secs]
2021-04-29T16:37:58.728+0800: 5.843: [GC (CMS Initial Mark) [1 CMS-initial-mark: 0K(1398144K)] 41154K(2027264K), 0.0076770 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
整體的結構和之前的GC日志相差并不大.
-
這段辨別目前GC的時間2021-04-29T16:37:58.684+0800: 5.799:
-
表明目前GC的原因,在這裡發送了2次,第一次是由于對象配置設定失敗,第二次是CMD的初始标記階段.因為CMS回收,即伴随着一次YuongGC,會對記憶體中的對象進行标記,這一行就表明目前YuongGC是由于CMS的初始标記引起的.GC (Allocation Failure)
-
很明顯,[ParNew: 559232K->26278K(629120K), 0.0439056 secs]
ParNew
回收器是收集年輕代的垃圾,這裡回收的就是年輕代的區域.
這裡是說,年輕代回收前是559M左右,回收後是26M左右,總共大小是629M左右.
後面的
表明GC的耗時0.0439056 secs
-
這段依舊是整個堆記憶體的占用情況,分别是GC前--->GC後(整個堆的記憶體大小),耗費時間.559232K->26278K(2027264K), 0.0440200 secs
-
這段就不說了,上面已經說過了,是GC的三種時間[Times: user=0.06 sys=0.01, real=0.04 secs]
-
這就是所謂的CMS的初始标記階段.标記所有可以到達GcRoot的對象2021-04-29T16:37:58.728+0800: 5.843: [GC (CMS Initial Mark) [1 CMS-initial-mark: 0K(1398144K)] 41154K(2027264K), 0.0076770 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
這一句說明,老年代的總大小是1398144K,在占用達到0K的時候觸發了CMSCMS-initial-mark: 0K(1398144K)] 41154K(2027264K)
3
2021-04-29T16:37:58.736+0800: 5.851: [CMS-concurrent-mark-start]
2021-04-29T16:37:58.744+0800: 5.859: [CMS-concurrent-mark: 0.008/0.008 secs] [Times: user=0.05 sys=0.00, real=0.01 secs]
2021-04-29T16:37:58.744+0800: 5.859: [CMS-concurrent-preclean-start]
2021-04-29T16:37:58.747+0800: 5.862: [CMS-concurrent-preclean: 0.003/0.003 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
2021-04-29T16:37:58.748+0800: 5.862: [CMS-concurrent-abortable-preclean-start]
2021-04-29T16:37:59.594+0800: 6.708: [CMS-concurrent-abortable-preclean: 0.458/0.846 secs] [Times: user=1.59 sys=0.08, real=0.85 secs]
這一段其實就是FullGC了,我們知道,使用了CMS垃圾回收器以,Old去的回收就是CMS來做的 .
-
這一段表示CMS的并發标記階段,在第一階段中停止的線程将再次啟動,并且在第一階段中标記的對象可傳遞到的所有對象都标記在這裡。2021-04-29T16:37:58.736+0800: 5.851: [CMS-concurrent-mark-start]
-
這一段是并發标記的時間位,可以看出并發标記的時間是0.008s2021-04-29T16:37:58.744+0800: 5.859: [CMS-concurrent-mark: 0.008/0.008 secs] [Times: user=0.05 sys=0.00, real=0.01 secs]
-
這兩條,上面的一條是辨別目前的階段是CMS的并發預清理階段,下面一條記錄了時間2021-04-29T16:37:58.744+0800: 5.859: [CMS-concurrent-preclean-start] 2021-04-29T16:37:58.747+0800: 5.862: [CMS-concurrent-preclean: 0.003/0.003 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
-
這兩條是可終止預清理的标記和持續的時間2021-04-29T16:37:58.748+0800: 5.862: [CMS-concurrent-abortable-preclean-start] 2021-04-29T16:37:59.594+0800: 6.708: [CMS-concurrent-abortable-preclean: 0.458/0.846 secs] [Times: user=1.59 sys=0.08, real=0.85 secs]
4
2021-04-29T16:37:59.594+0800: 6.708: [GC (CMS Final Remark) [YG occupancy: 394027 K (629120 K)]2021-04-29T16:37:59.594+0800: 6.709: [Rescan (parallel) , 0.1164667 secs]2021-04-29T16:37:59.710+0800: 6.825: [weak refs processing, 0.0000352 secs]2021-0
4-29T16:37:59.710+0800: 6.825: [class unloading, 0.0041990 secs]2021-04-29T16:37:59.715+0800: 6.829: [scrub symbol table, 0.0038982 secs]2021-04-29T16:37:59.719+0800: 6.833: [scrub string table, 0.0005058 secs][1 CMS-remark: 0K(1398144K)] 394027K(2027264K), 0.1260089 secs] [Times: user=0.29 sys=0.00, real=0.12 secs]
這一段比較重要,我們一起來看一下,首先這個階段是CMS最後一個STW階段,任務是完成标記整個老年代的所有存活對象.他的内容如下:
-
執行的時間021-04-29T16:37:59.594+0800
-
表明目前是最終的收集階段GC (CMS Final Remark)
-
年輕代的目前占用的情況和容量[YG occupancy: 394027 K (629120 K)]
-
完成存活對象的标記工作2021-04-29T16:37:59.594+0800: 6.709: [Rescan (parallel , 0.1164667 secs]
-
這個階段處理弱引用2021-04-29T16:37:59.710+0800: 6.825: [weak refs processing, 0.0000352 secs]
-
解除安裝類所用的時間2021-04-29T16:37:59.710+0800: 6.825: [class unloading, 0.0041990 secs]
-
清理字元串,中繼資料之類的占用2021-04-29T16:37:59.715+0800: 6.829: [scrub symbol table, 0.0038982 secs]
-
[1 CMS-remark: 0K(1398144K)] 394027K(2027264K), 0.1260089 secs] [Times: user=0.29 sys=0.00, real=0.12 secs]
這個階段以後,老年代的占用記憶體大小和容量(1398144),以及整個堆的大小和容量(2027264)
以及所耗時間.
5
2021-04-29T16:37:59.720+0800: 6.835: [CMS-concurrent-sweep-start]
2021-04-29T16:37:59.720+0800: 6.835: [CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2021-04-29T16:37:59.720+0800: 6.835: [CMS-concurrent-reset-start]
2021-04-29T16:37:59.791+0800: 6.906: [CMS-concurrent-reset: 0.071/0.071 secs] [Times: user=0.07 sys=0.05, real=0.07 secs]
-
這兩句是說,開始清除垃圾對象,這個時候,程式不是STW狀态(stop the word),後面一句是耗時2021-04-29T16:37:59.720+0800: 6.835: [CMS-concurrent-sweep-start] 2021-04-29T16:37:59.720+0800: 6.835: [CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
-
這裡是說,重新設定CMS算法内部的資料結構,準備下一個周期.也就是老年代清理完畢後的初始化.2021-04-29T16:37:59.720+0800: 6.835: [CMS-concurrent-reset-start] 2021-04-29T16:37:59.791+0800: 6.906: [CMS-concurrent-reset: 0.071/0.071 secs] [Times: user=0.07 sys=0.05, real=0.07 secs]
OK,基本上到這裡以後,所有的内容應該都有說過了.
ParNew和CMS的日志解析就到這裡了.下面是G1部分
3) G1 日志分析
因為我這篇博文的所有資料都是來源于實際的生産環境,但是目前還沒有使用到G1,我也不想用本地的資料來驗證,是以先空再這裡,後面生産環境實際使用G1以後會更新.
7. 結語
其實作階段有了K8S容器化技術以後,基本上小中型公司的JVM一般是不用做什麼大的調整的.我文中所示的,也是我們目前生産環境K8S容器中的真實資料,技術的學習在于不斷的積累和驗證,關于JVM調優,這隻是個開始,也不會結束.希望能記錄下來與君共勉.
其他日志詳細資訊,可以參考一下來自Oracle官方的部落格.
https://blogs.oracle.com/poonam/understanding-cms-gc-logs CMS
https://blogs.oracle.com/poonam/understanding-g1-gc-logs G1
個人公衆号<橙耘自留地>日前已經開通,後續部落客釋出的文章都會一并更新到公衆号,如有需要,自行查閱.
關于橙耘自留地,是我個人聚焦網際網路技術棧學習分享的一個平台,創立之初是因為目前業内各種技術課程資料層次不齊,褒貶不一,有時候一門課花費高價買入,其實内含草包,有時偶爾低價得之,卻又大有幹貨.是以我會根據大家的意見和評價,選擇不同的技術棧去學習,一為提升我自己的技術,二為大家梳理出品質比較好的課程,以作參考.同時,相關的學習心得也會一并更新到部落格和公衆号.