《Arthas進階》
學習目標
- 類和類載入器相關的命令
- monitor/watch/trace/stack等核心命令的使用
- 火焰圖的生成
- Arthas實戰案例
dump
作用
將已載入類的位元組碼檔案儲存到特定目錄:logs/arthas/classdump/
引數
數名稱 | 引數說明 |
---|---|
class-pattern | 類名錶達式匹配 |
[c:] |
類所屬 ClassLoader 的 hashcode |
[E] | 開啟正規表示式匹配,預設為萬用字元匹配 |
舉例
把String類的位元組碼檔案儲存到~/logs/arthas/classdump/目錄下
dump java.lang.String
把demo包下所有的類的位元組碼檔案儲存到~/logs/arthas/classdump/目錄下
dump demo.*
效果
小結
dump作用:將正在JVM中執行的程式的位元組碼檔案提取出來,儲存在logs相應的目錄下
不同的類載入器放在不同的目錄下
classloader
目標
獲取類載入器的資訊
作用
-
classloader
命令將 JVM 中所有的classloader的資訊統計出來,並可以展示繼承樹,urls等。 -
可以讓指定的classloader去getResources,列印出所有查詢到的resources的url。對於
ResourceNotFoundException
異常比較有用。
引數說明
引數名稱 | 引數說明 |
---|---|
[l] | 按類載入例項進行統計 |
[t] | 列印所有ClassLoader的繼承樹 |
[a] | 列出所有ClassLoader載入的類,請謹慎使用 |
[c:] |
ClassLoader的hashcode |
[c: r:] |
用ClassLoader去查詢resource |
[c: load:] |
用ClassLoader去載入指定的類 |
舉例
預設按類載入器的型別檢視統計資訊
classloader
按類載入器的例項檢視統計資訊,可以看到類載入的hashCode
classloader -l
檢視ClassLoader的繼承樹
classloader -t
透過類載入器的hash,檢視此類載入器實際所在的位置
classloader -c 680f2737
使用ClassLoader去查詢指定資源resource所在的位置
classloader -c 680f2737 -r META-INF/MANIFEST.MF
使用ClassLoader去查詢類的class檔案所在的位置
classloader -c 680f2737 -r java/lang/String.class
使用ClassLoader去載入類
classloader -c 70dea4e --load java.lang.String
小結
classloader命令主要作用有哪些?
- 顯示所有類載入器的資訊
- 獲取某個類載入器所在的jar包
- 獲取某個資源在哪個jar包中
- 載入某個類
monitor
目標
monitor命令:監控指定類中方法的執行情況
作用
對匹配
class-pattern
/method-pattern
的類、方法的呼叫進行監控。
monitor
命令是一個非實時返回命令,實時返回命令是輸入之後立即返回而非實時返回的命令,則是不斷的等待目標 Java 程序返回資訊,直到使用者輸入
Ctrl+C
為止。
引數說明
方法擁有一個命名引數 [c:]
,意思是統計週期(cycle of output),擁有一個整型的引數值
引數名稱 | 引數說明 |
---|---|
class-pattern | 類名錶達式匹配 |
method-pattern | 方法名錶達式匹配 |
[E] | 開啟正規表示式匹配,預設為萬用字元匹配 |
[c:] |
統計週期,預設值為120秒 |
舉例
過5秒監控一次,類demo.MathGame中primeFactors方法
monitor -c 5 demo.MathGame primeFactors
監控的維度說明
監控項 | 說明 |
---|---|
timestamp | 時間戳 |
class | Java類 |
method | 方法(構造方法、普通方法) |
total | 呼叫次數 |
success | 成功次數 |
fail | 失敗次數 |
rt | 平均耗時 |
fail-rate | 失敗率 |
小結
monitor命令的作用是什麼?
用來監視一個時間段中指定方法的執行次數,成功次數,失敗次數,耗時等這些資訊
watch
目標
觀察到指定方法的呼叫情況
作用
方法執行資料觀測,讓你能方便的觀察到指定方法的呼叫情況。
能觀察到的範圍為:
返回值
、丟擲異常
、入參
,透過編寫OGNL 表示式進行對應變數的檢視。
引數說明
watch 的引數比較多,主要是因為它能在 4 個不同的場景觀察物件
引數名稱 | 引數說明 |
---|---|
class-pattern | 類名錶達式匹配 |
method-pattern | 方法名錶達式匹配 |
express | 觀察表示式 |
condition-express | 條件表示式 |
[b] | 在方法呼叫之前觀察 before |
[e] | 在方法異常之後觀察 exception |
[s] | 在方法返回之後觀察 success |
[f] | 在方法結束之後(正常返回和異常返回)觀察 finish |
[E] | 開啟正規表示式匹配,預設為萬用字元匹配 |
[x:] | 指定輸出結果的屬性遍歷深度,預設為 1 |
這裡重點要說明的是觀察表示式,觀察表示式的構成主要由ognl 表示式組成,所以你可以這樣寫"{params,returnObj}"
,只要是一個合法的 ognl 表示式,都能被正常支援。
特別說明
- watch 命令定義了4個觀察事件點,即
-b
方法呼叫前,-e
方法異常後,-s
方法返回後,-f
方法結束後 - 4個觀察事件點
-b
、-e
、-s
預設關閉,-f
預設開啟,當指定觀察點被開啟後,在相應事件點會對觀察表示式進行求值並輸出 - 這裡要注意
方法入參
和方法出參
的區別,有可能在中間被修改導致前後不一致,除了-b
事件點params
代表方法入參外,其餘事件都代表方法出參 - 當使用
-b
時,由於觀察事件點是在方法呼叫前,此時返回值或異常均不存在
舉例
觀察demo.MathGame類中primeFactors方法出參和返回值,結果屬性遍歷深度為2。
params表示所有引數陣列(因為不確定是幾個引數),returnObject表示返回值
watch demo.MathGame primeFactors "{params,returnObj}" -x 2
觀察方法入參,對比前一個例子,返回值為空(事件點為方法執行前,因此獲取不到返回值)
watch demo.MathGame primeFactors "{params,returnObj}" -x 2 -b
觀察當前物件中的屬性,如果想檢視方法執行前後,當前物件中的屬性,可以使用target關鍵字,代表當前物件
watch demo.MathGame primeFactors 'target' -x 2
同時觀察方法呼叫前和方法返回後,引數裡-n 2,表示只執行兩次。
這裡輸出結果中,第一次輸出的是方法呼叫前的觀察表示式的結果,第二次輸出的是方法返回後的表示式的結果
params表示引數,target表示執行方法的物件,returnObject表示返回值
watch demo.MathGame primeFactors "{params,target,returnObj}" -x 2 -b -s -n 2
使用target.field_name訪問當前物件的某個屬性
watch demo.MathGame primeFactors 'target.illegalArgumentCount'
條件表示式的例子,輸出第1引數小於的情況
watch demo.MathGame primeFactors "{params[0],target}" "params[0]<0"
小結
引數名稱 | 引數說明 |
---|---|
[b] | begin 監視方法執行前的情況 |
[e] | exception 監視出現異常的情況 |
[s] | success 執行成功的情況 |
[f] | finish 執行完畢的情況,包含成功或失敗 |
trace
目標
學習trace這條命令的使用
對方法內部呼叫路徑進行追蹤,並輸出方法路徑上的每個節點上耗時
介紹
trace
命令能主動搜尋class-pattern
/method-pattern
對應的方法呼叫路徑,渲染和統計整個呼叫鏈路上的所有效能開銷和追蹤呼叫鏈路。觀察表示式的構成主要由ognl 表示式組成,所以你可以這樣寫
"{params,returnObj}"
,只要是一個合法的 ognl 表示式,都能被正常支援。很多時候我們只想看到某個方法的rt大於某個時間之後的trace結果,現在Arthas可以按照方法執行的耗時來進行過濾了,例如
trace *StringUtils isBlank '#cost>100'
表示當執行時間超過100ms的時候,才會輸出trace的結果。watch/stack/trace這個三個命令都支援
#cost
耗時條件過濾
引數說明
引數名稱 | 引數說明 |
---|---|
class-pattern | 類名錶達匹配 |
method-pattern | 方法名錶達式匹配 |
condition-express | 條件表示式,使用OGNL表示式 |
[E] | 開啟正規表示式匹配,預設是萬用字元匹配 |
[n:] |
設定命令執行次數 |
#cost |
方法執行耗時,單位是毫秒 |
舉例
trace函式指定類的指定方法
trace demo.MathGame run
如果方法呼叫的次數很多,那麼可以用-n引數指定捕捉結果的次數。比如下面的例子裡,捕捉到一次呼叫就退出命令。
trace demo.MathGame run -n 1
預設情況下,trace不會包含jdk裡的函式呼叫,如果希望trace jdk裡的函式,需要顯式設定--skipJDKMethod false。
trace --skipJDKMethod false demo.MathGame run
據呼叫耗時過濾,trace大於0.5ms的呼叫路徑
trace demo.MathGame run '#cost > .5'
可以用正則表匹配路徑上的多個類和函式,一定程度上達到多層trace的效果。
trace -E com.test.ClassA|org.test.ClassB method1|method2|method3
小結
引數名稱 | 引數說明 |
---|---|
class-pattern | 類名錶達式匹配 |
method-pattern | 方法名錶達式匹配 |
condition-express | 條件表示式 |
#cost | 過濾條件,只追蹤滿足的耗時方法 |
stack
作用
輸出當前方法被呼叫的呼叫路徑
介紹
很多時候我們都知道一個方法被執行,但這個方法被執行的路徑非常多,或者你根本就不知道這個方法是從那裡被執行了,此時你需要的是 stack 命令。
引數說明
引數名稱 | 引數說明 |
---|---|
class-pattern | 類名錶達式匹配 |
method-pattern | 方法名錶達式匹配 |
condition-express | 條件表示式,OGNL |
[E] | 開啟正規表示式匹配,預設為萬用字元匹配 |
[n:] |
執行次數限制 |
舉例
獲取primeFactors的呼叫路徑
stack demo.MathGame primeFactors
條件表示式來過濾,第0個引數的值小於0,-n表示獲取2次
stack demo.MathGame primeFactors 'params[0]<0' -n 2
據執行時間來過濾,耗時大於0.5毫秒
stack demo.MathGame primeFactors '#cost>0.5'
小結
stack命令的作用是什麼?
輸出當前方法被呼叫的路徑
tt
作用
time-tunnel 時間隧道
記錄下指定方法每次呼叫的入參和返回資訊,並能對這些不同時間下呼叫的資訊進行觀測
介紹
watch
雖然很方便和靈活,但需要提前想清楚觀察表示式的拼寫,這對排查問題而言要求太高,因為很多時候我們並不清楚問題出自於何方,只能靠蛛絲馬跡進行猜測。這個時候如果能記錄下當時方法呼叫的所有入參和返回值、丟擲的異常會對整個問題的思考與判斷非常有幫助。
於是乎,TimeTunnel 命令就誕生了。
引數解析
tt的引數 | 說明 |
---|---|
-t | 記錄某個方法在一個時間段中的呼叫 |
-l | 顯示所有已經記錄的列表 |
-n 次數 | 只記錄多少次 |
-s 表示式 | 搜尋表示式 |
-i 索引號 | 檢視指定索引號的詳細呼叫資訊 |
-p | 重新呼叫指定的索引號時間碎片 |
-
-t
tt 命令有很多個主引數,
-t
就是其中之一。這個參數列明希望記錄下類*Test
的print
方法的每次執行情況。 -
-n 3
當你執行一個呼叫量不高的方法時可能你還能有足夠的時間用
CTRL+C
中斷 tt 命令記錄的過程,但如果遇到呼叫量非常大的方法,瞬間就能將你的 JVM 記憶體撐爆。此時你可以透過
-n
引數指定你需要記錄的次數,當達到記錄次數時 Arthas 會主動中斷tt命令的記錄過程,避免人工操作無法停止的情況。
使用案例
基本使用
最基本的使用來說,就是記錄下當前方法的每次呼叫環境現場。
tt -t demo.MathGame primeFactors
表格欄位說明
表格欄位 | 欄位解釋 |
---|---|
INDEX | 時間片段記錄編號,每一個編號代表著一次呼叫,後續tt還有很多命令都是基於此編號指定記錄操作,非常重要。 |
TIMESTAMP | 方法執行的本機時間,記錄了這個時間片段所發生的本機時間 |
COST(ms) | 方法執行的耗時 |
IS-RET | 方法是否以正常返回的形式結束 |
IS-EXP | 方法是否以拋異常的形式結束 |
OBJECT | 執行物件的hashCode() ,注意,曾經有人誤認為是物件在JVM中的記憶體地址,但很遺憾他不是。但他能幫助你簡單的標記當前執行方法的類實體 |
CLASS | 執行的類名 |
METHOD | 執行的方法名 |
-
條件表示式
不知道大家是否有在使用過程中遇到以下困惑
- Arthas 似乎很難區分出過載的方法
- 我只需要觀察特定引數,但是 tt 卻全部都給我記錄了下來
條件表示式也是用
OGNL
來編寫,核心的判斷物件依然是Advice
物件。除了tt
命令之外,watch
、trace
、stack
命令也都支援條件表示式。 -
解決方法過載
tt -t *Test print params.length==1
透過制定引數個數的形式解決不同的方法簽名,如果引數個數一樣,你還可以這樣寫
tt -t *Test print 'params[1] instanceof Integer'
-
解決指定引數
tt -t *Test print params[0].mobile=="13989838402"
檢索呼叫記錄
當你用 tt
記錄了一大片的時間片段之後,你希望能從中篩選出自己需要的時間片段,這個時候你就需要對現有記錄進行檢索。
tt -l
需要篩選出 primeFactors
方法的呼叫資訊
tt -s 'method.name=="primeFactors"'
檢視呼叫資訊
對於具體一個時間片的資訊而言,你可以透過 -i
引數後邊跟著對應的 INDEX
編號檢視到他的詳細資訊。
tt -i 1002
重做一次呼叫
當你稍稍做了一些調整之後,你可能需要前端系統重新觸發一次你的呼叫,此時得求爺爺告奶奶的需要前端配合聯調的同學再次發起一次呼叫。而有些場景下,這個呼叫不是這麼好觸發的。
tt
命令由於儲存了當時呼叫的所有現場資訊,所以我們可以自己主動對一個 INDEX
編號的時間片自主發起一次呼叫,從而解放你的溝通成本。此時你需要 -p
引數。透過 --replay-times
指定 呼叫次數,透過 --replay-interval
指定多次呼叫間隔(單位ms, 預設1000ms)
tt -i 1002 -p
小結
作用:記錄指定方法每次呼叫的入參和返回值,並後期還可以對這些資訊進行觀測
tt的引數 | 說明 |
---|---|
-t | 記錄方法在一個時間段中呼叫 |
-l | 顯示所有已經記錄的列表 |
-n 次數 | 只記錄多少次 |
-s 搜尋表示式 | 指定搜尋表示式 |
-i 索引號 | 檢視它的詳細資訊 |
options
作用
全域性開關
全域性選項
名稱 | 預設值 | 描述 |
---|---|---|
unsafe | false | 是否支援對系統級別的類進行增強,開啟該開關可能導致把JVM搞掛,請慎重選擇! |
dump | false | 是否支援被增強了的類dump到外部檔案中,如果開啟開關,class檔案會被dump到/${application dir}/arthas-class-dump/ 目錄下,具體位置詳見控制檯輸出 |
batch-re-transform | true | 是否支援批次對匹配到的類執行retransform操作 |
json-format | false | 是否支援json化的輸出 |
disable-sub-class | false | 是否禁用子類匹配,預設在匹配目標類的時候會預設匹配到其子類,如果想精確匹配,可以關閉此開關 |
debug-for-asm | false | 列印ASM相關的除錯資訊 |
save-result | false | 是否開啟執行結果存日誌功能,開啟之後所有命令的執行結果都將儲存到~/logs/arthas-cache/result.log 中 |
job-timeout | 1d | 非同步後臺任務的預設超時時間,超過這個時間,任務自動停止;比如設定 1d, 2h, 3m, 25s,分別代表天、小時、分、秒 |
print-parent-fields | true | 是否列印在parent class裡的filed |
案例
檢視所有的options
options
獲取option的值
options json-format
設定指定的option
例如,想開啟執行結果存日誌功能,輸入如下命令即可:
options save-result true
小結
options的作用是:檢視或設定arthas全域性環境變數
profiler火焰圖
目標
生成火焰圖
介紹
profiler
命令支援生成應用熱點的火焰圖。本質上是透過不斷的取樣,然後把收集到的取樣結果生成火焰圖。
命令基本執行結構是 profiler 命令 [命令引數]
案例
啟動profiler
$ profiler start
Started [cpu] profiling
預設情況下,生成的是cpu的火焰圖,即event為
cpu
。可以用--event
引數來指定。
顯示支援的事件
$ profiler list
獲取已採集的sample的數量
$ profiler getSamples
23
檢視profiler狀態
$ profiler status
[cpu] profiling is running for 4 seconds
可以檢視當前profiler在取樣哪種event
和取樣時間。
停止profiler
生成svg格式結果
$ profiler stop
profiler output file: /tmp/demo/arthas-output/20191125-135546.svg
OK
預設情況下,生成的結果儲存到應用的工作目錄
下的arthas-output
目錄。可以透過 --file
引數來指定輸出結果路徑。比如:
$ profiler stop --file /tmp/output.svg
profiler output file: /tmp/output.svg
OK
生成html格式結果
預設情況下,結果檔案是svg
格式,如果想生成html
格式,可以用--format
引數指定:
$ profiler stop --format html
profiler output file: /tmp/test/arthas-output/20191125-143329.html
OK
或者在--file
引數裡用檔名指名格式。比如--file /tmp/result.html
。
透過瀏覽器檢視arthas-output下面的profiler結果
預設情況下,arthas使用3658埠,則可以開啟: http://localhost:3658/arthas-output/ 檢視到arthas-output
目錄下面的profiler結果:
點選可以檢視具體的結果:
火焰圖的含義
火焰圖是基於 perf 結果產生的SVG 圖片,用來展示 CPU 的呼叫棧。
y 軸表示呼叫棧,每一層都是一個函式。呼叫棧越深,火焰就越高,頂部就是正在執行的函式,下方都是它的父函式。
x 軸表示抽樣數,如果一個函式在 x 軸佔據的寬度越寬,就表示它被抽到的次數多,即執行的時間長。注意,x 軸不代表時間,而是所有的呼叫棧合併後,按字母順序排列的。
火焰圖就是看頂層的哪個函式佔據的寬度最大。只要有"平頂"(plateaus),就表示該函式可能存在效能問題。
顏色沒有特殊含義,因為火焰圖表示的是 CPU 的繁忙程度,所以一般選擇暖色調。
小結
profiler | 命令作用 |
---|---|
profiler start | 啟動profiler,預設情況下,生成cpu的火焰圖 |
profiler list | 顯示所有支援的事件 |
profiler getSamples | 獲取已採集的sample的數量 |
profiler status | 檢視profiler的狀態,執行的時間 |
profiler stop | 停止profiler,生成火焰圖的結果,指定輸出目錄和輸出格式:svg或html |
Arthas實踐
需求
1. 哪個Controller處理了請求
我們可以快速定位一個請求是被哪些Filter
攔截的,或者請求最終是由哪些Servlet
處理的。但有時,我們想知道一個請求是被哪個Spring MVC Controller處理的。如果翻程式碼的話,會比較難找,並且不一定準確。透過Arthas可以精確定位是哪個Controller
處理請求。
2. 每個請求的呼叫引數和返回值是多少
透過watch來檢視請求的引數和返回值
準備場景
將ssm_student.war專案部署到Linux的tomcat伺服器下,可以正常訪問。
啟動之後,訪問:http://192.168.254.199:8080/ssm_student ,會返回如下頁面。192.168.254.199 是Linux伺服器的地址。
那麼這個請求是被哪個Controller
處理的呢?
步驟
- trace定位DispatcherServlet
- jad反編譯DispatcherServlet
- watch定位handler
- 使用watch得到方法的入參和返回值
實現步驟
第1步:
在瀏覽器上進行登入操作,檢查最耗時的方法
trace *.DispatcherServlet *
可以分步trace,請求最終是被DispatcherServlet#doDispatch()處理了
trace *.FrameworkServlet doService
第2步:
trace結果裡把呼叫的行號列印出來了,我們可以直接在IDE裡檢視程式碼(也可以用jad命令反編譯)
jad --source-only *.DispatcherServlet doDispatch
第3步:
watch *.DispatcherServlet getHandler 'returnObj'
檢視返回的結果,得到使用到了2個控制器的方法
第4步:
watch com.itheima.controller.* * {params,returnObj} -x 2
結論
透過trace, jad, watch最後得到這個操作由2個控制器來處理,分別是:
com.itheima.controller.UserController.login()
com.itheima.controller.StudentController.findAll()
學習總結
命令 | 說明 |
---|---|
dump | 將已載入類的位元組碼檔案儲存到特定的目錄中 |
classloader | 獲取類載入器的資訊 |
monitor | 監控指定類中方法的執行情況 |
watch | 觀察到指定方法的呼叫情況 |
trace | 對方法內部呼叫路徑進行追蹤,並輸出方法路徑上每個節點上耗時 |
stack | 輸出當前方法被呼叫的路徑 |
tt | 記錄指定方法每次呼叫的入參和返回資訊 |
options | 全域性開關 |
profiler | 生成火焰圖 |