<JVM下篇:效能監控與調優篇>01-概述篇-02-JVM監控及診斷工具-命令列篇

2021-05-07 03:00:25

https://gitee.com/vectorx/NOT...

https://codechina.csdn.net/qq...

https://github.com/uxiahnan/N...

[TOC]

1. 概述篇

1.1. 大廠面試題

<mark>支付寶:</mark>

支付寶三面:JVM效能調優都做了什麼?

<mark>小米:</mark>

有做過JVM記憶體優化嗎?

從SQL、JVM、架構、資料庫四個方面講講優化思路

<mark>螞蟻金服:</mark>

JVM的編譯優化

jvm效能調優都做了什麼

JVM診斷調優工具用過哪些?

二面:jvm怎樣調優,堆記憶體、棧空間設定多少合適

三面:JVM相關的分析工具使用過的有哪些?具體的效能調優步驟如何

<mark>阿里:</mark>

如何進行JVM調優?有哪些方法?

如何理解記憶體漏失問題?有哪些情況會導致記憶體漏失?如何解決?

<mark>位元組跳動:</mark>

三面:JVM如何調優、引數怎麼調?

<mark>拼多多:</mark>

從SQL、JVM、架構、資料庫四個方面講講優化思路

<mark>京東:</mark>

JVM診斷調優工具用過哪些?

每秒幾十萬並行的秒殺系統為什麼會頻繁發生GC?

日均百萬級交易系統如何優化JVM?

線上生產系統OOM如何監控及定位與解決?

高並行系統如何基於G1垃圾回收器優化效能?

1.2. 背景說明

生產環境中的問題

  • 生產環境發生了記憶體溢位該如何處理?
  • 生產環境應該給伺服器分配多少記憶體合適?
  • 如何對垃圾回收器的效能進行調優?
  • 生產環境CPU負載飆高該如何處理?
  • 生產環境應該給應用分配多少執行緒合適?
  • 不加log,如何確定請求是否執行了某一行程式碼?
  • 不加log,如何實時檢視某個方法的入參與返回值?

為什麼要調優

  • 防止出現OOM
  • 解決OOM
  • 減少Full GC出現的頻率

不同階段的考慮

  • 上線前
  • 專案執行階段
  • 線上出現OOM

1.3. 調優概述

監控的依據

  • 執行紀錄檔
  • 異常堆疊
  • GC紀錄檔
  • 執行緒快照
  • 堆轉儲快照

調優的大方向

  • 合理地編寫程式碼
  • 充分併合理的使用硬體資源
  • 合理地進行JVM調優

1.4. 效能優化的步驟

第1步:效能監控

  • GC頻繁
  • cpu load過高
  • OOM
  • 記憶體洩露
  • 死鎖
  • 程式響應時間較長

第2步:效能分析

  • 列印GC紀錄檔,通過GCviewer或者 http://gceasy.io 來分析異常資訊
  • 靈活運用命令列工具、jstack、jmap、jinfo等
  • dump出堆檔案,使用記憶體分析工具分析檔案
  • 使用阿里Arthas、jconsole、JVisualVM來實時檢視JVM狀態
  • jstack檢視堆疊資訊

第3步:效能調優

  • 適當增加記憶體,根據業務背景選擇垃圾回收器
  • 優化程式碼,控制記憶體使用
  • 增加機器,分散節點壓力
  • 合理設定執行緒池執行緒數量
  • 使用中介軟體提高程式效率,比如快取、訊息佇列等
  • 其他……

1.5. 效能評價/測試指標

停頓時間(或響應時間)

提交請求和返回該請求的響應之間使用的時間,一般比較關注平均響應時間。常用操作的響應時間列表:

操作響應時間
開啟一個站點幾秒
資料庫查詢一條記錄(有索引)十幾毫秒
機械磁碟一次定址定位4毫秒
從機械磁碟順序讀取1M資料2毫秒
從SSD磁碟順序讀取1M資料0.3毫秒
從遠端分散式換成Redis 讀取一個資料0.5毫秒
從記憶體讀取 1M資料十幾微妙
Java程式本地方法呼叫幾微妙
網路傳輸2Kb資料1 微妙

在垃圾回收環節中:

  • 暫停時間:執行垃圾收集時,程式的工作執行緒被暫停的時間。
  • -XX:MaxGCPauseMillis

吞吐量

  • 對單位時間內完成的工作量(請求)的量度
  • 在GC中:執行使用者程式碼的事件佔總執行時間的比例(總執行時間:程式的執行時間+記憶體回收的時間)
  • 吞吐量為1-1/(1+n),其中-XX::GCTimeRatio=n

並行數

  • 同一時刻,對伺服器有實際互動的請求數

記憶體佔用

  • Java堆區所佔的記憶體大小

相互間的關係

以高速公路通行狀況為例

  • 吞吐量:每天通過高速公路收費站的車輛的資料
  • 並行數:高速公路上正在行駛的車輛的數目
  • 響應時間:車速

<hr/>

2. JVM監控及診斷工具-命令列篇

2.1. 概述

效能診斷是軟體工程師在日常工作中需要經常面對和解決的問題,在使用者體驗至上的今天,解決好應用的效能問題能帶來非常大的收益。

Java 作為最流行的程式語言之一,其應用效能診斷一直受到業界廣泛關注。可能造成 Java 應用出現效能問題的因素非常多,例如執行緒控制、磁碟讀寫、資料庫存取、網路I/O、垃圾收集等。想要定位這些問題,一款優秀的效能診斷工具必不可少。

體會1:使用資料說明問題,使用知識分析問題,使用工具處理問題。

體會2:無監控、不調優!

簡單命令列工具

在我們剛接觸java學習的時候,大家肯定最先了解的兩個命令就是javac,java,那麼除此之外,還有沒有其他的命令可以供我們使用呢?

我們進入到安裝jdk的bin目錄,發現還有一系列輔助工具。這些輔助工具用來獲取目標 JVM 不同方面、不同層次的資訊,幫助開發人員很好地解決Java應用程式的一些疑難雜症。

image-20210504195803526

image-20210504195836342

官方原始碼地址:http://hg.openjdk.java.net/jdk/jdk11/file/1ddf9a99e4ad/src/jdk.jcmd/share/classes/sun/tools

2.2. jps:檢視正在執行的Java程序

jps(Java Process Status):顯示指定系統內所有的HotSpot虛擬機器器程序(檢視虛擬機器器程序資訊),可用於查詢正在執行的虛擬機器器程序。

說明:對於本地虛擬機器器程序來說,程序的本地虛擬機器器ID與作業系統的程序ID是一致的,是唯一的。

基本使用語法為:jps [options] [hostid]

我們還可以通過追加引數,來列印額外的資訊。

options引數

  • -q:僅僅顯示LVMID(local virtual machine id),即本地虛擬機器器唯一id。不顯示主類的名稱等
  • -l:輸出應用程式主類的全類名 或 如果程序執行的是jar包,則輸出jar完整路徑
  • -m:輸出虛擬機器器程序啟動時傳遞給主類main()的引數
  • -v:列出虛擬機器器程序啟動時的JVM引數。比如:-Xms20m -Xmx50m是啟動程式指定的jvm引數。

說明:以上引數可以綜合使用。

補充:如果某 Java 程序關閉了預設開啟的UsePerfData引數(即使用引數-XX:-UsePerfData),那麼jps命令(以及下面介紹的jstat)將無法探知該Java 程序。

hostid引數

RMI登入檔中註冊的主機名。如果想要遠端監控主機上的 java 程式,需要安裝 jstatd。

對於具有更嚴格的安全實踐的網路場所而言,可能使用一個自定義的策略檔案來顯示對特定的可信主機或網路的存取,儘管這種技術容易受到IP地址欺詐攻擊。

如果安全問題無法使用一個客製化的策略檔案來處理,那麼最安全的操作是不執行jstatd伺服器,而是在本地使用jstat和jps工具。

2.3. jstat:檢視JVM統計資訊

jstat(JVM Statistics Monitoring Tool):用於監視虛擬機器器各種執行狀態資訊的命令列工具。它可以顯示本地或者遠端虛擬機器器程序中的類裝載、記憶體、垃圾收集、JIT編譯等執行資料。在沒有GUI圖形介面,只提供了純文字控制檯環境的伺服器上,它將是執行期定位虛擬機器器效能問題的首選工具。常用於檢測垃圾回收問題以及記憶體漏失問題。

官方檔案:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jstat.html

基本使用語法為:jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]

檢視命令相關引數:jstat-h 或 jstat-help

其中vmid是程序id號,也就是jps之後看到的前面的號碼,如下:

image-20210504201703222

option引數

選項option可以由以下值構成。

<mark>類裝載相關的:</mark>

  • -class:顯示ClassLoader的相關資訊:類的裝載、解除安裝數量、總空間、類裝載所消耗的時間等

<mark>垃圾回收相關的:</mark>

  • -gc:顯示與GC相關的堆資訊。包括Eden區、兩個Survivor區、老年代、永久代等的容量、已用空間、GC時間合計等資訊。
  • -gccapacity:顯示內容與-gc基本相同,但輸出主要關注Java堆各個區域使用到的最大、最小空間。
  • -gcutil:顯示內容與-gc基本相同,但輸出主要關注已使用空間佔總空間的百分比。
  • -gccause:與-gcutil功能一樣,但是會額外輸出導致最後一次或當前正在發生的GC產生的原因。
  • -gcnew:顯示新生代GC狀況
  • -gcnewcapacity:顯示內容與-gcnew基本相同,輸出主要關注使用到的最大、最小空間
  • -geold:顯示老年代GC狀況
  • -gcoldcapacity:顯示內容與-gcold基本相同,輸出主要關注使用到的最大、最小空間
  • -gcpermcapacity:顯示永久代使用到的最大、最小空間。

<mark>JIT相關的:</mark>

  • -compiler:顯示JIT編譯器編譯過的方法、耗時等資訊
  • -printcompilation:輸出已經被JIT編譯的方法

jstat -class

img

jstat -compiler

img

jstat -printcompilation

img

jstat -gc

img

jstat -gccapacity

img

jstat -gcutil

img

jstat -gccause

img

jstat -gcnew

img

jstat -gcnewcapacity

img

jstat -gcold

img

jstat -gcoldcapacity

img

jstat -t

img

jstat -t -h

img

表頭含義(位元組)
ECEden區的大小
EUEden區已使用的大小
S0C倖存者0區的大小
S1C倖存者1區的大小
S0U倖存者0區已使用的大小
S1U倖存者1區已使用的大小
MC元空間的大小
MU元空間已使用的大小
OC老年代的大小
OU老年代已使用的大小
CCSC壓縮類空間的大小
CCSU壓縮類空間已使用的大小
YGC從應用程式啟動到取樣時young gc的次數
YGCT從應用程式啟動到取樣時young gc消耗時間(秒)
FGC從應用程式啟動到取樣時full gc的次數
FGCT從應用程式啟動到取樣時的full gc的消耗時間(秒)
GCT從應用程式啟動到取樣時gc的總時間

interval引數: 用於指定輸出統計資料的週期,單位為毫秒。即:查詢間隔

count引數: 用於指定查詢的總次數

-t引數: 可以在輸出資訊前加上一個Timestamp列,顯示程式的執行時間。單位:秒

-h引數: 可以在週期性資料輸出時,輸出多少行資料後輸出一個表頭資訊

補充: jstat還可以用來判斷是否出現記憶體漏失。

第1步:在長時間執行的 Java 程式中,我們可以執行jstat命令連續獲取多行效能資料,並取這幾行資料中 OU 列(即已佔用的老年代記憶體)的最小值。

第2步:然後,我們每隔一段較長的時間重複一次上述操作,來獲得多組 OU 最小值。如果這些值呈上漲趨勢,則說明該 Java 程式的老年代記憶體已使用量在不斷上漲,這意味著無法回收的物件在不斷增加,因此很有可能存在記憶體漏失。

2.4. jinfo:實時檢視和修改JVM設定引數

jinfo(Configuration Info for Java):檢視虛擬機器器設定引數資訊,也可用於調整虛擬機器器的設定引數。在很多情況卡,Java應用程式不會指定所有的Java虛擬機器器引數。而此時,開發人員可能不知道某一個具體的Java虛擬機器器引數的預設值。在這種情況下,可能需要通過查詢檔案獲取某個引數的預設值。這個查詢過程可能是非常艱難的。但有了jinfo工具,開發人員可以很方便地找到Java虛擬機器器引數的當前值。

基本使用語法為:jinfo [options] pid

說明:java 程序ID必須要加上

選項選項說明
no option輸出全部的引數和系統屬性
-flag name輸出對應名稱的引數
-flag [+-]name開啟或者關閉對應名稱的引數 只有被標記為manageable的引數才可以被動態修改
-flag name=value設定對應名稱的引數
-flags輸出全部的引數
-sysprops輸出系統屬性

jinfo -sysprops

> jinfo -sysprops
jboss.modules.system.pkgs = com.intellij.rt
java.vendor = Oracle Corporation
sun.java.launcher = SUN_STANDARD
sun.management.compiler = HotSpot 64-Bit Tiered Compilers
catalina.useNaming = true
os.name = Windows 10
...

jinfo -flags

> jinfo -flags 25592
Non-default VM flags: -XX:CICompilerCount=4 -XX:InitialHeapSize=333447168 -XX:MaxHeapSize=5324668928 -XX:MaxNewSize=1774714880 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=111149056 -XX:OldSize=222298112 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
Command line:  -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:8040,suspend=y,server=n -Drebel.base=C:\Users\Vector\.jrebel -Drebel.env.ide.plugin.version=2021.1.2 -Drebel.env.ide.version=2020.3.3 -Drebel.env.ide.product=IU -Drebel.env.ide=intellij -Drebel.notification.url=http://localhost:7976 -agentpath:C:\Users\Vector\AppData\Roaming\JetBrains\IntelliJIdea2020.3\plugins\jr-ide-idea\lib\jrebel6\lib\jrebel64.dll -Dmaven.home=D:\eclipse\env\maven -Didea.modules.paths.file=C:\Users\Vector\AppData\Local\JetBrains\IntelliJIdea2020.3\Maven\idea-projects-state-596682c7.properties -Dclassworlds.conf=C:\Users\Vector\AppData\Local\Temp\idea-6755-mvn.conf -Dmaven.ext.class.path=D:\IDEA\plugins\maven\lib\maven-event-listener.jar -javaagent:D:\IDEA\plugins\java\lib\rt\debugger-agent.jar -Dfile.encoding=UTF-8

jinfo -flag

> jinfo -flag UseParallelGC 25592
-XX:+UseParallelGC

> jinfo -flag UseG1GC 25592
-XX:-UseG1GC

jinfo -flag name

> jinfo -flag UseParallelGC 25592
-XX:+UseParallelGC

> jinfo -flag UseG1GC 25592
-XX:-UseG1GC

jinfo -flag [+-]name

> jinfo -flag +PrintGCDetails 25592
> jinfo -flag PrintGCDetails 25592
-XX:+PrintGCDetails

> jinfo -flag -PrintGCDetails 25592
> jinfo -flag PrintGCDetails 25592
-XX:-PrintGCDetails

拓展:

  • java -XX:+PrintFlagsInitial 檢視所有JVM引數啟動的初始值

    [Global flags]
         intx ActiveProcessorCount                      = -1                                  {product}
        uintx AdaptiveSizeDecrementScaleFactor          = 4                                   {product}
        uintx AdaptiveSizeMajorGCDecayTimeScale         = 10                                  {product}
        uintx AdaptiveSizePausePolicy                   = 0                                   {product}
    ...
  • java -XX:+PrintFlagsFinal 檢視所有JVM引數的最終值

    [Global flags]
         intx ActiveProcessorCount                      = -1                                  {product}
    ...
         intx CICompilerCount                          := 4                                   {product}
        uintx InitialHeapSize                          := 333447168                           {product}
        uintx MaxHeapSize                              := 1029701632                          {product}
        uintx MaxNewSize                               := 1774714880                          {product}
  • java -XX:+PrintCommandLineFlags 檢視哪些已經被使用者或者JVM設定過的詳細的XX引數的名稱和值

    -XX:InitialHeapSize=332790016 -XX:MaxHeapSize=5324640256 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC 

2.5. jmap:匯出記憶體映像檔案&記憶體使用情況

jmap(JVM Memory Map):作用一方面是獲取dump檔案(堆轉儲快照檔案,二進位制檔案),它還可以獲取目標Java程序的記憶體相關資訊,包括Java堆各區域的使用情況、堆中物件的統計資訊、類載入資訊等。開發人員可以在控制檯中輸入命令「jmap -help」查閱jmap工具的具體使用方式和一些標準選項設定。

官方幫助檔案:https://docs.oracle.com/en/java/javase/11/tools/jmap.html

基本使用語法為:

  • jmap [option] <pid>
  • jmap [option] <executable <core>
  • jmap [option] [server_id@] <remote server IP or hostname>
選項作用
-dump生成dump檔案(Java堆轉儲快照),-dump:live只儲存堆中的存活物件
-heap輸出整個堆空間的詳細資訊,包括GC的使用、堆設定資訊,以及記憶體的使用資訊等
-histo輸出堆空間中物件的統計資訊,包括類、範例數量和合計容量,-histo:live只統計堆中的存活物件
-J <flag>傳遞引數給jmap啟動的jvm
-finalizerinfo顯示在F-Queue中等待Finalizer執行緒執行finalize方法的物件,僅linux/solaris平臺有效
-permstat以ClassLoader為統計口徑輸出永久代的記憶體狀態資訊,僅linux/solaris平臺有效
-F當虛擬機器器程序對-dump選項沒有任何響應時,強制執行生成dump檔案,僅linux/solaris平臺有效

說明:這些引數和linux下輸入顯示的命令多少會有不同,包括也受jdk版本的影響。

> jmap -dump:format=b,file=<filename.hprof> <pid>> jmap -dump:live,format=b,file=<filename.hprof> <pid>

由於jmap將存取堆中的所有物件,為了保證在此過程中不被應用執行緒干擾,jmap需要藉助安全點機制,讓所有執行緒停留在不改變堆中資料的狀態。也就是說,由jmap匯出的堆快照必定是安全點位置的。這可能導致基於該堆快照的分析結果存在偏差。

舉個例子,假設在編譯生成的機器碼中,某些物件的生命週期在兩個安全點之間,那麼:live選項將無法探知到這些物件。

另外,如果某個執行緒長時間無法跑到安全點,jmap將一直等下去。與前面講的jstat則不同,垃圾回收器會主動將jstat所需要的摘要資料儲存至固定位置之中,而jstat只需直接讀取即可。

2.6. jhat:JDK自帶堆分析工具

jhat(JVM Heap Analysis Tool):Sun JDK提供的jhat命令與jmap命令搭配使用,用於分析jmap生成的heap dump檔案(堆轉儲快照)。jhat內建了一個微型的HTTP/HTML伺服器,生成dump檔案的分析結果後,使用者可以在瀏覽器中檢視分析結果(分析虛擬機器器轉儲快照資訊)。

使用了jhat命令,就啟動了一個http服務,埠是7000,即http://localhost:7000/,就可...

說明:jhat命令在JDK9、JDK10中已經被刪除,官方建議用VisualVM代替。

基本適用語法:jhat <option> <dumpfile>

option引數作用
-stack false|true關閉|開啟物件分配呼叫棧跟蹤
-refs false|true關閉|開啟物件參照跟蹤
-port port-number設定jhat HTTP Server的埠號,預設7000
-exclude exclude-file執行物件查詢時需要排除的資料成員
-baseline exclude-file指定一個基準堆轉儲
-debug int設定debug級別
-version啟動後顯示版本資訊就退出
-J <flag>傳入啟動引數,比如-J-Xmx512m

2.7. jstack:列印JVM中執行緒快照

jstack(JVM Stack Trace):用於生成虛擬機器器指定程序當前時刻的執行緒快照(虛擬機器器堆疊跟蹤)。執行緒快照就是當前虛擬機器器內指定程序的每一條執行緒正在執行的方法堆疊的集合。

生成執行緒快照的作用:可用於定位執行緒出現長時間停頓的原因,如執行緒間死鎖、死迴圈、請求外部資源導致的長時間等待等問題。這些都是導致執行緒長時間停頓的常見原因。當執行緒出現停頓時,就可以用jstack顯示各個執行緒呼叫的堆疊情況。

官方幫助檔案:https://docs.oracle.com/en/java/javase/11/tools/jstack.html

在thread dump中,要留意下面幾種狀態

  • <mark>死鎖,Deadlock(重點關注)</mark>
  • <mark>等待資源,Waiting on condition(重點關注)</mark>
  • <mark>等待獲取監視器,Waiting on monitor entry(重點關注)</mark>
  • <mark>阻塞,Blocked(重點關注)</mark>
  • 執行中,Runnable
  • 暫停,Suspended
  • 物件等待中,Object.wait() 或 TIMED_WAITING
  • 停止,Parked
option引數作用
-F當正常輸出的請求不被響應時,強制輸出執行緒堆疊
-l除堆疊外,顯示關於鎖的附加資訊
-m如果呼叫本地方法的話,可以顯示C/C++的堆疊

2.8. jcmd:多功能命令列

在JDK 1.7以後,新增了一個命令列工具jcmd。它是一個多功能的工具,可以用來實現前面除了jstat之外所有命令的功能。比如:用它來匯出堆、記憶體使用、檢視Java程序、匯出執行緒資訊、執行GC、JVM執行時間等。

官方幫助檔案:https://docs.oracle.com/en/java/javase/11/tools/jcmd.html

jcmd擁有jmap的大部分功能,並且在Oracle的官方網站上也推薦使用jcmd命令代jmap命令

jcmd -l:列出所有的JVM程序

jcmd 程序號 help:針對指定的程序,列出支援的所有具體命令

image-20210504213044819

jcmd 程序號 具體命令:顯示指定程序的指令命令的資料

  • Thread.print 可以替換 jstack指令
  • GC.class_histogram 可以替換 jmap中的-histo操作
  • GC.heap_dump 可以替換 jmap中的-dump操作
  • GC.run 可以檢視GC的執行情況
  • VM.uptime 可以檢視程式的總執行時間,可以替換jstat指令中的-t操作
  • VM.system_properties 可以替換 jinfo -sysprops 程序id
  • VM.flags 可以獲取JVM的設定引數資訊

2.9. jstatd:遠端主機資訊收集

之前的指令只涉及到監控本機的Java應用程式,而在這些工具中,一些監控工具也支援對遠端計算機的監控(如jps、jstat)。為了啟用遠端監控,則需要配合使用jstatd 工具。命令jstatd是一個RMI伺服器端程式,它的作用相當於代理伺服器,建立本地計算機與遠端監控工具的通訊。jstatd伺服器將本機的Java應用程式資訊傳遞到遠端計算機。

image-20210504213301077