啟動arthas:
wget https://alibaba.github.io/arthas/arthas-boot.jar
使用aliyun的鏡像:java -jar arthas-boot.jar --repo-mirror aliyun --use-http
java -jar arthas-boot.jar <pid>
sudo -u tomcat java -jar arthas-boot.jar
手動可參考 https://alibaba.github.io/arthas/manual-install.html
1:獲取安裝包
wget https://maven.aliyun.com/repository/public/com/taobao/arthas/arthas-packaging/3.0.4/arthas-packaging-3.0.4-bin.zip
2:解壓安裝包
unzip arthas-packaging-3.0.4-bin.zip
3:安裝
./install-local.sh
4:運行
./as.sh
或者 sudo -u tomcat -EH ./as.sh
遇到的坑:
- 下載腳本不要放在家目錄,用戶之前無法互相訪問家目錄。腳本放在自己家目錄 tomcat無法訪問你的家目錄,會執(zhí)行出錯
- 執(zhí)行用戶需要和java用戶保持身份一致。
如果找不到進程,可以如下方式:
curl -L https://alibaba.github.io/arthas/install.sh | sh
命令:
查看dashboard
通過thread命令來獲取到arthas-demo進程的Main Class
thread 1會打印線程ID 1的棧,通常是main函數(shù)的線程。
$ thread 1 | grep 'main('
at demo.MathGame.main(MathGame.java:17)
通過jad來反編繹Main Class
$ jad demo.MathGame
watch命令
通過watch命令來查看demo.MathGame#primeFactors函數(shù)的返回值
$ watch demo.MathGame primeFactors returnObj
觀察方法出參和返回值
$ watch demo.MathGame primeFactors "{params,returnObj}" -x 2
觀察方法入?yún)?$ watch demo.MathGame primeFactors "{params,returnObj}" -x 2 -b
條件限制 - #只有滿足條件的調(diào)用,才會有響應(yīng)
$ watch demo.MathGame primeFactors "{params[0],target}" "params[0]<0"
觀察異常信息的例子
$ watch demo.MathGame primeFactors "{params[0],throwExp}" -e -x 2
按照耗時進行過濾 - #cost>200(單位是ms)
#watch/stack/trace這個三個命令都支持#cost
$ watch demo.MathGame primeFactors '{params, returnObj}' '#cost>200' -x 2
觀察當(dāng)前對象中的屬性
$ watch demo.MathGame primeFactors 'target'
使用target.field_name訪問當(dāng)前對象的某個屬性
$ watch demo.MathGame primeFactors 'target.illegalArgumentCount'
觀察類中所有拋出異常的方法信息
$ watch com.example.UserService * -e -x 2 '{params,throwExp}'
同時觀察方法調(diào)用前和方法返回后
#參數(shù)里-n 2,表示只執(zhí)行兩次
#結(jié)果的輸出順序和事件發(fā)生的先后順序一致,和命令中 -s -b 的順序無關(guān)
#-x表示遍歷深度,可以調(diào)整來打印具體的參數(shù)和結(jié)果內(nèi)容,默認值是1
$ watch demo.MathGame primeFactors "{params,target,returnObj}" -x 2 -b -s -n 2
特別說明
- watch 命令定義了4個觀察事件點,即 -b 方法調(diào)用前,-e 方法異常后,-s 方法返回后,-f 方法結(jié)束后
- 4個觀察事件點 -b、-e、-s 默認關(guān)閉,-f 默認打開,當(dāng)指定觀察點被打開后,在相應(yīng)事件點會對觀察表達式進行求值并輸出
- 這里要注意方法入?yún)⒑头椒ǔ鰠⒌膮^(qū)別,有可能在中間被修改導(dǎo)致前后不一致,除了 -b 事件點 params 代表方法入?yún)⑼猓溆嗍录即矸椒ǔ鰠?/li>
- 當(dāng)使用 -b 時,由于觀察事件點是在方法調(diào)用前,此時返回值或異常均不存在
觀察表達式的構(gòu)成主要由 ognl 表達式組成,所以你可以這樣寫"{params,returnObj}",只要是一個合法的 ognl 表達式,都能被正常支持。觀察的維度也比較多,主要體現(xiàn)在參數(shù) advice 的數(shù)據(jù)結(jié)構(gòu)上。Advice 參數(shù)最主要是封裝了通知節(jié)點的所有信息。
用法:https://alibaba.github.io/arthas/advice-class.html
如果只是退出當(dāng)前的連接,可以用quit或者exit命令。Attach到目標進程上的arthas還會繼續(xù)運行,端口會保持開放,下次連接時可以直接連接上。
如果想完全退出arthas,可以執(zhí)行shutdown命令。
jvm相關(guān)
- dashboard——當(dāng)前系統(tǒng)的實時數(shù)據(jù)面板
- thread——查看當(dāng)前 JVM 的線程堆棧信息
- jvm——查看當(dāng)前 JVM 的信息
- sysprop——查看和修改JVM的系統(tǒng)屬性
- sysenv——查看JVM的環(huán)境變量
- getstatic——查看類的靜態(tài)屬性
- New! ognl——執(zhí)行ognl表達式
mc 編譯指令
Memory Compiler/內(nèi)存編繹器,編繹.java文件生成.class。
mc /tmp/Test.java
可以通過-c參數(shù)指定classloader:
mc -c 327a647b /tmp/Test.java
可以通過-d命令指定輸出目錄:
mc -d /tmp/output /tmp/ClassA.java /tmp/ClassB.java
編繹生成.class文件之后,可以結(jié)合redefine命令實現(xiàn)熱更新代碼
動態(tài)增加日志 redefine
在Arthas里,可以通過redefine命令來達到線上不重啟,動態(tài)更新代碼的效果
添加日志,本地編繹后,把.class文件傳到線上服務(wù)器,然后用redefine命令來更新代碼
$ redefine -p /tmp/UserServiceImpl.class
redefine success, size: 1
三個命令結(jié)合使用 用過vim等編輯后最終redefine
jad --source-only com.example.demo.arthas.user.UserController > /tmp/UserController.java
mc /tmp/UserController.java -d /tmp
redefine /tmp/com/example/demo/arthas/user/UserController.class
注意:
- 正在跑的函數(shù),沒有退出不能生效 比如while(true)循環(huán)
- 不允許新增加field/method
記錄調(diào)用
$ tt -t com.example.webtest.controller.testController testResult
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 12 ms.
INDEX TIMESTAMP COST(ms) IS-RET IS-EXP OBJECT CLASS METHOD
----------------------------------------------------------------------------------------------------------------------------------------------------------
1002 2019-02-12 16:14:25 0.367999 true false 0x6ad213f7 testController testResult
命令參數(shù)解析
-t
tt 命令有很多個主參數(shù),-t 就是其中之一。這個參數(shù)的表明希望記錄下類 *Test 的 print 方法的每次執(zhí)行情況。
-n 3
當(dāng)你執(zhí)行一個調(diào)用量不高的方法時可能你還能有足夠的時間用 CTRL+C 中斷 tt 命令記錄的過程,但如果遇到調(diào)用量非常大的方法,瞬間就能將你的 JVM 內(nèi)存撐爆。
此時你可以通過 -n 參數(shù)指定你需要記錄的次數(shù),當(dāng)達到記錄次數(shù)時 Arthas 會主動中斷tt命令的記錄過程,避免人工操作無法停止的情況。
解決方法重載
tt -t *Test print params[0].length==1
通過制定參數(shù)個數(shù)的形式解決不同的方法簽名,如果參數(shù)個數(shù)一樣,你還可以這樣寫
tt -t *Test print 'params[1] instanceof Integer'
解決指定參數(shù)
tt -t *Test print params[0].mobile=="13989838402"
查看檢索到的記錄
tt -l
查看調(diào)用具體信息
tt -i 1003
對于具體一個時間片的信息而言,你可以通過 -i 參數(shù)后邊跟著對應(yīng)的 INDEX 編號查看到他的詳細信息
回放調(diào)用
tt -i 1004 -p
$ tt -i 1004 -p
RE-INDEX 1004
GMT-REPLAY 2018-12-04 11:26:00
OBJECT 0x4b67cf4d
CLASS demo.MathGame
METHOD primeFactors
PARAMETERS[0] @Integer[946738738]
IS-RETURN true
IS-EXCEPTION false
RETURN-OBJ @ArrayList[
@Integer[2],
@Integer[11],
@Integer[17],
@Integer[2531387],
]
Time fragment[1004] successfully replayed.
Affect(row-cnt:1) cost in 14 ms.
tt 命令由于保存了當(dāng)時調(diào)用的所有現(xiàn)場信息,所以我們可以自己主動對一個 INDEX 編號的時間片自主發(fā)起一次調(diào)用,從而解放你的溝通成本。此時你需要 -p 參數(shù)。
需要強調(diào)的點
- ThreadLocal 信息丟失
很多框架偷偷的將一些環(huán)境變量信息塞到了發(fā)起調(diào)用線程的 ThreadLocal 中,由于調(diào)用線程發(fā)生了變化,這些 ThreadLocal 線程信息無法通過 Arthas 保存,所以這些信息將會丟失。
一些常見的 CASE 比如:鷹眼的 TraceId 等。 - 引用的對象
需要強調(diào)的是,tt 命令是將當(dāng)前環(huán)境的對象引用保存起來,但僅僅也只能保存一個引用而已。如果方法內(nèi)部對入?yún)⑦M行了變更,或者返回的對象經(jīng)過了后續(xù)的處理,那么在 tt 查看的時候?qū)o法看到當(dāng)時最準確的值。這也是為什么 watch 命令存在的意義。
獲取Spring context
除了上面介紹的一些排查技巧,下面分享一個獲取Spring Context,然后“為所欲為”的例子。
在Dubbo里有一個擴展com.alibaba.dubbo.config.spring.extension.SpringExtensionFactory,把Spring Context保存到了里面。
因此,我們可以通過ognl命令獲取到。
$ ognl '#context=@com.alibaba.dubbo.config.spring.extension.SpringExtensionFactory@contexts.iterator.next, #context.getBean("userServiceImpl").findUser(1)'
@User[
id=@Integer[1],
name=@String[Deanna Borer],
]
SpringExtensionFactory@contexts.iterator.next 獲取到SpringExtensionFactory里保存的spring context對象
#context.getBean("userServiceImpl").findUser(1) 獲取到userServiceImpl再執(zhí)行一次調(diào)用
Dubbo運行時有哪些Filter? 耗時是多少?
trace函數(shù)
-j 過濾掉jdk的函數(shù)
$ trace -j demo.MathGame run
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 31 ms.
`---ts=2018-12-04 01:09:14;thread_name=main;id=1;is_daemon=false;priority=5;TCCL=sun.misc.Launcher$AppClassLoader@3d4eac69
`---[5.190646ms] demo.MathGame:run()
+---[4.465779ms] demo.MathGame:primeFactors()
`---[0.375324ms] demo.MathGame:print()
據(jù)調(diào)用耗時過濾
trace demo.MathGame run '#cost > 10'
$ trace com.alibaba.dubbo.rpc.Filter *
Press Ctrl+C to abort.
Affect(class-cnt:19 , method-cnt:59) cost in 1441 ms.
`---ts=2018-12-05 19:07:26;thread_name=DubboServerHandler-30.5.125.152:20880-thread-10;id=3e;is_daemon=true;priority=5;TCCL=sun.misc.Launcher$AppClassLoader@5c647e05
`---[8.435844ms] com.alibaba.dubbo.rpc.filter.EchoFilter:invoke()
+---[0.124572ms] com.alibaba.dubbo.rpc.Invocation:getMethodName()
+---[0.065123ms] java.lang.String:equals()
`---[7.762928ms] com.alibaba.dubbo.rpc.Invoker:invoke()
`---[7.494124ms] com.alibaba.dubbo.rpc.filter.ClassLoaderFilter:invoke()
+---[min=0.00355ms,max=0.049922ms,total=0.057637ms,count=3] java.lang.Thread:currentThread()
+---[0.0126ms] java.lang.Thread:getContextClassLoader()
+---[0.02188ms] com.alibaba.dubbo.rpc.Invoker:getInterface()
+---[0.004115ms] java.lang.Class:getClassLoader()
+---[min=0.003906ms,max=0.014058ms,total=0.017964ms,count=2] java.lang.Thread:setContextClassLoader()
`---[7.033486ms] com.alibaba.dubbo.rpc.Invoker:invoke()
`---[6.869488ms] com.alibaba.dubbo.rpc.filter.GenericFilter:invoke()
+---[0.01481ms] com.alibaba.dubbo.rpc.Invocation:getMethodName()
動態(tài)修改Dubbo的logger級別
在排查問題時,需要查看到更多的信息,如果可以把logger級別修改為DEBUG,就非常有幫助。
ognl是apache開源的一個輕量級表達式引擎。下面通過Arthas里的ognl命令來動態(tài)修改logger級別。
首先獲取Dubbo里TraceFilter的一個logger對象,看下它的實現(xiàn)類,可以發(fā)現(xiàn)是log4j
$ ognl '@com.alibaba.dubbo.rpc.protocol.dubbo.filter.TraceFilter@logger.logger'
@Log4jLogger[
FQCN=@String[com.alibaba.dubbo.common.logger.support.FailsafeLogger],
logger=@Logger[org.apache.log4j.Logger@2f19bdcf],
]
再用sc命令來查看具體從哪個jar包里加載的
$ sc -d org.apache.log4j.Logger
class-info org.apache.log4j.Logger
code-source /Users/hengyunabc/.m2/repository/org/slf4j/log4j-over-slf4j/1.7.25/log4j-over-slf4j-1.7.25.jar
name org.apache.log4j.Logger
isInterface false
isAnnotation false
isEnum false
isAnonymousClass false
isArray false
isLocalClass false
isMemberClass false
isPrimitive false
isSynthetic false
simple-name Logger
modifier public
annotation
interfaces
super-class +-org.apache.log4j.Category
+-java.lang.Object
class-loader +-sun.misc.Launcher$AppClassLoader@5c647e05
+-sun.misc.Launcher$ExtClassLoader@59878d35
classLoaderHash 5c647e05
Affect(row-cnt:1) cost in 126 ms.
可以看到log4j是通過slf4j代理的。
那么通過org.slf4j.LoggerFactory獲取root logger,再修改它的level:
$ ognl '@org.slf4j.LoggerFactory@getLogger("root").setLevel(@ch.qos.logback.classic.Level@DEBUG)'
null
$ ognl '@org.slf4j.LoggerFactory@getLogger("root").getLevel().toString()'
@String[DEBUG]
sc命令
“Search-Class” 的簡寫,這個命令能搜索出所有已經(jīng)加載到 JVM 中的 Class 信息
打印類的詳細信息
sc -d com.example.webtest.controller.testController
打印出類的Field信息
sc -d -f demo.MathGame
ognl
https://commons.apache.org/proper/commons-ognl/language-guide.html
$ ognl '#value1=@System@getProperty("java.home"), #value2=@System@getProperty("java.runtime.name"), {#value1, #value2}'
@ArrayList[
@String[/opt/java/8.0.181-zulu/jre],
@String[OpenJDK Runtime Environment],
]