Arthas是什么
Arthas 是Alibaba開源的Java診斷工具,深受開發(fā)者喜愛。
當你遇到以下類似問題而束手無策時,Arthas可以幫助你解決:
這個類從哪個 jar 包加載的?為什么會報各種類相關(guān)的 Exception?
遇到問題無法在線上 debug,難道只能通過加日志再重新發(fā)布嗎?
線上遇到某個用戶的數(shù)據(jù)處理有問題,但線上同樣無法 debug,線下無法重現(xiàn)!
——Arthas中文說明文檔
前幾天遇到了一個這樣的場景:
- 核心業(yè)務(wù)上線了新功能,在上線時間節(jié)點前后核心方法平均耗時同比增長了約15%
- 新功能包含一個第三方RPC調(diào)用,有可能出現(xiàn)服務(wù)不穩(wěn)定的風(fēng)險
- 新功能未增加耗時日志記錄,第三方RPC調(diào)用也未接入統(tǒng)計上報,導(dǎo)致在服務(wù)器日志及RPC鏈路監(jiān)控中都沒辦法了解耗時信息
- 新功能成了一個監(jiān)控盲點,上線前后耗時又有變化,于是這個功能成了首要懷疑對象
這是一個典型的線上Debug場景:加打點日志,重新發(fā)布。
Arthas作為一個診斷工具,提供了很多更高效的方法。
Arthas能做什么
Arthas通過JDK提供的Instrument
(基于Java Virtual Machine Tool Interface
)和asm庫(操作字節(jié)碼),在低侵入的情況下提供了非常豐富的在線診斷功能,包括JVM信息查詢、字節(jié)碼增強熱替換等。
幾個比較關(guān)鍵的功能是:
- 查看JVM的一些信息,包括線程信息、加載的類的信息、系統(tǒng)屬性和環(huán)境變量等
- 反編譯已加載類的代碼(.class to .java)
- 內(nèi)存編譯器(.java to .class)
- 加載外部.class文件并替換原本已加載的類
通過這幾個關(guān)鍵功能,Arthas可以實現(xiàn)無需重啟(甚至不需要登錄對應(yīng)的服務(wù)器)在線上環(huán)境Debug、臨時緊急修復(fù)等功能。
Arthas功能的確很豐富,但是使用不當也會造成一定的影響。
基礎(chǔ)命令
help
查看命令幫助
pwd
當前工作目錄,同Linux命令,結(jié)合cat命令使用
cat
打印文件內(nèi)容,同Linux命令,可用于查看機器配置內(nèi)容
history
歷史命令,同Linux命令
dashboard
實時數(shù)據(jù),包括線程信息、堆棧信息、系統(tǒng)變量
比較重要的命令
sc 和 sm
-
sc {Class-Pattern}
Search-Class,搜索JVM已經(jīng)加載的類,比如sc *Controller
-
sm {Class-Pattern}
Search-Method,搜索類中的方法,比如sm *QuestionController
getstatic
可以用于查看類的靜態(tài)屬性,比如用于查看當前加載的配置信息
$ getstatic *ConfigUtil editWhiteList
@HashSet[
@Long[174446],
@Long[175009],
@Long[174957],
@Long[21959],
@Long[10519],
@Long[23813],
@Long[10034],
]
jad、mc、redefine
-
jad [--source-only] {Class-Pattern} [{Method}]
將指定的類反編譯為高亮代碼,可結(jié)合sm命令僅反編譯指定方法 -
mc {File}
Memory-Compile,使用內(nèi)存編譯器將.java文件編譯為.class文件 -
redefine
加載外部.class文件,替換JVM中的類
通過這三個方法的結(jié)合可以實現(xiàn)在線反編譯、修改、編譯、替換的熱修復(fù)操作,但是實際上這個做法十分危險,很可能導(dǎo)致線上代碼處于不可控狀態(tài)。
更合理的方式是在本地修復(fù)并編譯通過后將生成的.class文件在線上替換。
thread
-
thread
查看當前JVM所有線程信息,ID一欄是JVM級別的。
$ thread
Threads Total: 160, NEW: 0, RUNNABLE: 16, BLOCKED: 0, WAITING: 109, TIMED_WAITING: 35, TERMINATED: 0
ID NAME GROUP PRIORITY STATE %CPU TIME INTERRUPTED DAEMON
284 as-command-execute-daemon system 10 RUNNABLE 56 0:0 false true
34 cluster-ClusterId{value='5d31369656d4890864c2d2ff', descri main 5 TIMED_WAITING 36 0:0 false true
36 DubboRegistryFailedRetryTimer-thread-1 main 5 TIMED_WAITING 2 0:0 false true
50 DubboResponseTimeoutScanTimer main 5 TIMED_WAITING 1 0:2 false true
54 JinJingRocketMQSender-thread-1 main 5 TIMED_WAITING 1 0:0 false true
可以看出來在Arthas接入期間診斷用的線程CPU占用率是比較高的。
-
thread {id}
查看指定線程的狀態(tài)
$ thread 284
"as-command-execute-daemon" Id=284 RUNNABLE
at sun.management.ThreadImpl.dumpThreads0(Native Method)
at sun.management.ThreadImpl.getThreadInfo(ThreadImpl.java:440)
at com.taobao.arthas.core.command.monitor200.ThreadCommand.processThread(ThreadCommand.java:146)
at com.taobao.arthas.core.command.monitor200.ThreadCommand.process(ThreadCommand.java:77)
at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl.process(AnnotatedCommandImpl.java:82)
at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl.access$100(AnnotatedCommandImpl.java:18)
at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl$ProcessHandler.handle(AnnotatedCommandImpl.java:111)
at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl$ProcessHandler.handle(AnnotatedCommandImpl.java:108)
at com.taobao.arthas.core.shell.system.impl.ProcessImpl$CommandProcessTask.run(ProcessImpl.java:370)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)
Number of locked synchronizers = 1
- java.util.concurrent.ThreadPoolExecutor$Worker@5c49c9aa
-
thread -n {n}
打印最忙的n個線程的信息
$ thread -n 3
"as-command-execute-daemon" Id=289 cpuUsage=95% RUNNABLE
at sun.management.ThreadImpl.dumpThreads0(Native Method)
at sun.management.ThreadImpl.getThreadInfo(ThreadImpl.java:440)
at com.taobao.arthas.core.command.monitor200.ThreadCommand.processTopBusyThreads(ThreadCommand.java:133)
at com.taobao.arthas.core.command.monitor200.ThreadCommand.process(ThreadCommand.java:79)
at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl.process(AnnotatedCommandImpl.java:82)
at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl.access$100(AnnotatedCommandImpl.java:18)
at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl$ProcessHandler.handle(AnnotatedCommandImpl.java:111)
at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl$ProcessHandler.handle(AnnotatedCommandImpl.java:108)
at com.taobao.arthas.core.shell.system.impl.ProcessImpl$CommandProcessTask.run(ProcessImpl.java:370)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)
Number of locked synchronizers = 1
- java.util.concurrent.ThreadPoolExecutor$Worker@4b3798a4
"DubboResponseTimeoutScanTimer" Id=50 cpuUsage=2% TIMED_WAITING
at java.lang.Thread.sleep(Native Method)
at com.alibaba.dubbo.remoting.exchange.support.DefaultFuture$RemotingInvocationTimeoutScan.run(DefaultFuture.java:300)
at java.lang.Thread.run(Thread.java:745)
"Abandoned connection cleanup thread" Id=23 cpuUsage=1% TIMED_WAITING on java.lang.ref.ReferenceQueue$Lock@47459f1b
at java.lang.Object.wait(Native Method)
- waiting on java.lang.ref.ReferenceQueue$Lock@47459f1b
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:135)
at com.mysql.jdbc.AbandonedConnectionCleanupThread.run(AbandonedConnectionCleanupThread.java:40)
-
thread -b
找出阻塞其他線程的線程
* 非常重要的命令
monitor
使用了字節(jié)碼增強并替換了原本運行中的類,用于查看方法的執(zhí)行統(tǒng)計信息,包括調(diào)用次數(shù)、平均耗時、失敗率
$ monitor -c 5 demo.MathGame primeFactors
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 94 ms.
timestamp class method total success fail avg-rt(ms) fail-rate
-----------------------------------------------------------------------------------------------
2018-12-03 19:06:38 demo.MathGame primeFactors 5 1 4 1.15 80.00%
timestamp class method total success fail avg-rt(ms) fail-rate
-----------------------------------------------------------------------------------------------
2018-12-03 19:06:43 demo.MathGame primeFactors 5 3 2 42.29 40.00%
timestamp class method total success fail avg-rt(ms) fail-rate
-----------------------------------------------------------------------------------------------
2018-12-03 19:06:48 demo.MathGame primeFactors 5 3 2 67.92 40.00%
timestamp class method total success fail avg-rt(ms) fail-rate
-----------------------------------------------------------------------------------------------
2018-12-03 19:06:53 demo.MathGame primeFactors 5 2 3 0.25 60.00%
timestamp class method total success fail avg-rt(ms) fail-rate
-----------------------------------------------------------------------------------------------
2018-12-03 19:06:58 demo.MathGame primeFactors 1 1 0 0.45 0.00%
timestamp class method total success fail avg-rt(ms) fail-rate
-----------------------------------------------------------------------------------------------
2018-12-03 19:07:03 demo.MathGame primeFactors 2 2 0 3182.72 0.00%
trace
使用了字節(jié)碼增強并替換了原本運行中的類,用于查看方法的執(zhí)行耗時,對當前方法調(diào)用的每個方法都進行了打點計時,并標記最耗時的調(diào)用,只追蹤一級方法
$ trace demo.MathGame run
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 42 ms.
`---ts=2018-12-04 00:44:17;thread_name=main;id=1;is_daemon=false;priority=5;TCCL=sun.misc.Launcher$AppClassLoader@3d4eac69
`---[10.611029ms] demo.MathGame:run()
+---[0.05638ms] java.util.Random:nextInt()
+---[10.036885ms] demo.MathGame:primeFactors()
`---[0.170316ms] demo.MathGame:print()
reset
monitor、trace命令都使用了字節(jié)碼增強替換,替換后的類邏輯上沒有變化,但是插入的一些切面有些許額外性能開銷的,不應(yīng)當追蹤過長的時間,在診斷完成之后應(yīng)當使用reset方法恢復(fù)所有增強過的類。
shutdown
關(guān)閉當前的Arthas客戶端,同時關(guān)閉當前連接,在關(guān)閉時會調(diào)用reset。
30分鐘未進行操作會自動shutdown,超時時間可配置。這應(yīng)該是考慮到Arthas不應(yīng)當長時間啟用、連接,如果忘記退出需要自動reset。
quit
關(guān)閉當前的Arthas客戶端,不會關(guān)閉當前連接,不會調(diào)用reset。
Arthas怎么用
方式1:使用jar
操作流程
- 登錄目標機器
-
java -jar arthas-boot.jar
(或者使用/path/to/java -jar /path/to/arthas-boot.jar --arthas-home /path/to/arthas/lib/
) - 選擇要連接的進程id,Arthas會進行attach和連接
attach和連接是兩個步驟,attach是在目標進程中創(chuàng)建一個Arthas的守護線程,并監(jiān)聽對應(yīng)的端口,默認為8563。
使用jar操作的一些坑
- Arthas和服務(wù)最好使用對應(yīng)的JDK版本
- Arthas啟動時會訪問Maven倉庫下載依賴(默認使用阿里云,可配置),考慮到機房隔離問題,最好在需要部署的機器上將
arthas-boot.jar
和其依賴提前下載一起復(fù)制過去
方式2:使用WebConsole
操作流程
-
進入Arthas控制臺(需自己實現(xiàn))
-
選擇目標服務(wù),使用Arthas提供的WebConsole遠程連接
使用 WebConsole 遠程連接時的一些坑
- 目標機器Arthas未attach時無法連接,且需要確認attach的具體PID
- 需要一臺Arthas客戶端用于提供WebConsole服務(wù)
- 其實也可以直接訪問目標機器的8563端口,但考慮到機房的隔離問題,最好使用一個跳板機,通過跳板機連接目標機器
- 連接到同一臺機器的客戶端會共用一個連接,因此連接其他服務(wù)前應(yīng)當斷開當前連接
-
quit
命令不會斷開連接,需要shutdown
,而其中一個客戶端使用shutdown
所有共用連接的客戶端都會退出
目前為了使用 WebConsole 做了哪些措施
- 修改了目標機器上原有的一個Java Agent,提供了一個Http接口,用于:
- 喚起目標機器上的Arthas
- 查找PID并attach到目標進程
- 開發(fā)Arthas控制臺,在控制臺點擊“啟用Arthas”后做了一系列操作:
- 如果本機Arthas未啟動,喚起控制臺本機的Arthas
- 調(diào)用目標機器Agent的喚起接口,attach到對應(yīng)的進程
- 從控制臺機器上的Arthas客戶端連接目標機器的Arthas監(jiān)聽的端口
后續(xù)可優(yōu)化的點
- 可以集成到其他系統(tǒng)中,比如發(fā)布平臺、監(jiān)控平臺
- 可以增加熱修復(fù)的功能(需非常謹慎)
- 目前agent提供Http接口用于喚醒Arthas,跨機房的喚醒和連接需要建立機房間的代理或在每個機房都部署控制臺
- 目前使用Http接口喚醒目標機器的Arthas,可以改為監(jiān)聽Zookeeper的方式喚醒
Arthas相關(guān)網(wǎng)站
Github:alibaba/arthas: Alibaba Java Diagnostic Tool Arthas/Alibaba Java診斷利器Arthas
中文文檔,有在線教程:arthas/README_CN.md at master · alibaba/arthas
命令介紹:進階使用 — Arthas 3.1.1 文檔
參考資料
記錄如何使用arthas進行遠程訪問 · Issue #442 · alibaba/arthas
本文搬自我的博客,歡迎參觀!