Arthas 進階教程
啟動math-game
下載demo-arthas-spring-boot.jar
,再用java -jar
命令啟動:
wget https://github.com/hengyunabc/spring-boot-inside/raw/master/demo-arthas-spring-boot/demo-arthas-spring-boot.jar java -jar demo-arthas-spring-boot.jar
demo-arthas-spring-boot
是一個很簡單的spring boot應用,原始碼:檢視
啟動之後,可以訪問80埠: https://2886795286-80-host11nc.environments.katacoda.com
啟動arthas-boot
在新的Terminal 2
裡,下載arthas-boot.jar
,再用java -jar
命令啟動:
wget https://arthas.aliyun.com/arthas-boot.jar java -jar arthas-boot.jar --target-ip 0.0.0.0
arthas-boot
是Arthas
的啟動程式,它啟動後,會列出所有的Java程式,使用者可以選擇需要診斷的目標程式。
選擇第一個程式,輸入 1
,再Enter/回車
:
Attach成功之後,會列印Arthas LOGO。輸入 help
可以獲取到更多的幫助資訊。
檢視JVM資訊
下面介紹Arthas裡檢視JVM
資訊的命令。
sysprop
sysprop
可以列印所有的System Properties資訊。
也可以指定單個key: sysprop java.version
也可以通過grep
來過濾: sysprop | grep user
可以設定新的value: sysprop testKey testValue
sysenv
sysenv
命令可以獲取到環境變數。和sysprop
命令類似。
jvm
jvm
命令會列印出JVM
的各種詳細資訊。
dashboard
dashboard
命令可以檢視當前系統的實時資料皮膚。
輸入 Q
或者 Ctrl+C
可以退出dashboard命令。
Tips
為了更好使用Arthas,下面先介紹Arthas裡的一些使用技巧。
help
Arthas裡每一個命令都有詳細的幫助資訊。可以用-h
來檢視。幫助資訊裡有EXAMPLES
和WIKI
連結。
比如:
sysprop -h
自動補全
Arthas支援豐富的自動補全功能,在使用有疑惑時,可以輸入Tab
來獲取更多資訊。
比如輸入 sysprop java.
之後,再輸入Tab
,會補全出對應的key:
$ sysprop java.
sc/sm 檢視已載入的類
下面介紹Arthas裡查詢已載入類的命令。
sc
sc
命令可以查詢到所有JVM已經載入到的類。
如果搜尋的是介面,還會搜尋所有的實現類。比如檢視所有的Filter
實現類:
sc javax.servlet.Filter
通過-d
引數,可以列印出類載入的具體資訊,很方便查詢類載入問題。
sc -d javax.servlet.Filter
sc
支援通配,比如搜尋所有的StringUtils
:
sc *StringUtils
sm
sm
命令則是查詢類的具體函式。比如:
sm java.math.RoundingMode
通過-d
引數可以列印函式的具體屬性:
sm -d java.math.RoundingMode
也可以查詢特定的函式,比如查詢建構函式:
sm java.math.RoundingMode <init>
Jad
可以通過 jad
命令來反編譯程式碼:
jad com.example.demo.arthas.user.UserController
通過--source-only
引數可以只列印出在反編譯的原始碼:
jad --source-only com.example.demo.arthas.user.UserController
Ognl -> 重要
在Arthas裡,有一個單獨的ognl
命令,可以動態執行程式碼。
呼叫static函式
ognl '@java.lang.System@out.println("hello ognl")'
可以檢查Terminal 1
(不是arthas的Terminal 2)裡的程式輸出,可以發現列印出了hello ognl
。
查詢UserController的ClassLoader
sc -d com.example.demo.arthas.user.UserController | grep classLoaderHash$ sc -d com.example.demo.arthas.user.UserController | grep classLoaderHash classLoaderHash 1be6f5c3
注意hashcode是變化的,需要先檢視當前的ClassLoader資訊,提取對應ClassLoader的hashcode。
如果你使用-c
,你需要手動輸入hashcode:-c <hashcode>
$ ognl -c 1be6f5c3 @com.example.demo.arthas.user.UserController@logger
對於只有唯一例項的ClassLoader可以通過--classLoaderClass
指定class name,使用起來更加方便:
$ ognl --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader @org.springframework.boot.SpringApplication@logger@Slf4jLocationAwareLog[ FQCN=@String[org.apache.commons.logging.LogAdapter$Slf4jLocationAwareLog], name=@String[org.springframework.boot.SpringApplication], logger=@Logger[Logger[org.springframework.boot.SpringApplication]],]
--classLoaderClass
的值是ClassLoader的類名,只有匹配到唯一的ClassLoader例項時才能工作,目的是方便輸入通用命令,而-c <hashcode>
是動態變化的。
獲取靜態類的靜態欄位
獲取UserController
類裡的logger
欄位:
ognl --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader @com.example.demo.arthas.user.UserController@logger
還可以通過-x
引數控制返回值的展開層數。比如:
ognl --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader -x 2 @com.example.demo.arthas.user.UserController@logger
執行多行表示式,賦值給臨時變數,返回一個List
ognl '#value1=@System@getProperty("java.home"), #value2=@System@getProperty("java.runtime.name"), {#value1, #value2}'$ ognl '#value1=@System@getProperty("java.home"), #value2=@System@getProperty("java.runtime.name"), {#value1, #value2}'@ArrayList[ @String[/Library/Java/JavaVirtualMachines/jdk1.8.0_162.jdk/Contents/Home/jre], @String[Java(TM) SE Runtime Environment],]
更多
在Arthas裡ognl
表示式是很重要的功能,在很多命令裡都可以使用ognl
表示式。
一些更復雜的用法,可以參考:
- OGNL特殊用法請參考:https://github.com/alibaba/arthas/issues/71
- OGNL表示式官方指南:https://commons.apache.org/proper/commons-ognl/language-guide.html
案例: 排查函式呼叫異常
現象
目前,訪問 http://localhost/user/0 ,會返回500異常:
curl http://localhost/user/0{"timestamp":1550223186170,"status":500,"error":"Internal Server Error","exception":"java.lang.IllegalArgumentException","message":"id < 1","path":"/user/0"}
但請求的具體引數,異常棧是什麼呢?
檢視UserController的 引數/異常
在Arthas裡執行:
watch com.example.demo.arthas.user.UserController * '{params, throwExp}'
- 第一個引數是類名,支援通配
- 第二個引數是函式名,支援通配
訪問 curl http://localhost/user/0
,watch
命令會列印呼叫的引數和異常
$ watch com.example.demo.arthas.user.UserController * '{params, throwExp}'Press Q or Ctrl+C to abort.Affect(class-cnt:1 , method-cnt:2) cost in 53 ms.ts=2019-02-15 01:35:25; [cost=0.996655ms] result=@ArrayList[ @Object[][isEmpty=false;size=1], @IllegalArgumentException[java.lang.IllegalArgumentException: id < 1],]
可以看到實際丟擲的異常是IllegalArgumentException
。
可以輸入 Q
或者 Ctrl+C
退出watch命令。
如果想把獲取到的結果展開,可以用-x
引數:
watch com.example.demo.arthas.user.UserController * '{params, throwExp}' -x 2
返回值表示式
在上面的例子裡,第三個引數是返回值表示式
,它實際上是一個ognl
表示式,它支援一些內建物件:
- loader
- clazz
- method
- target
- params
- returnObj
- throwExp
- isBefore
- isThrow
- isReturn
你可以利用這些內建物件來組成不同的表示式。比如返回一個陣列:
watch com.example.demo.arthas.user.UserController * '{params[0], target, returnObj}'
更多參考: https://arthas.aliyun.com/doc/advice-class.html
條件表示式
watch
命令支援在第4個引數裡寫條件表示式,比如:
watch com.example.demo.arthas.user.UserController * returnObj 'params[0] > 100'
當訪問 https://2886795301-80-host12nc.environments.katacoda.com/user/1 時,watch
命令沒有輸出
當訪問 https://2886795301-80-host12nc.environments.katacoda.com/user/101 時,watch
會列印出結果。
$ watch com.example.demo.arthas.user.UserController * returnObj 'params[0] > 100'Press Q or Ctrl+C to abort.Affect(class-cnt:1 , method-cnt:2) cost in 47 ms.ts=2019-02-13 19:42:12; [cost=0.821443ms] result=@User[ id=@Integer[101], name=@String[name101],]
當異常時捕獲
watch
命令支援-e
選項,表示只捕獲丟擲異常時的請求:
watch com.example.demo.arthas.user.UserController * "{params[0],throwExp}" -e
按照耗時進行過濾
watch命令支援按請求耗時進行過濾,比如:
watch com.example.demo.arthas.user.UserController * '{params, returnObj}' '#cost>200'
案例: 熱更新程式碼
下面介紹通過jad
/mc
/redefine
命令實現動態更新程式碼的功能。
目前,訪問 http://localhost/user/0 ,會返回500異常:
curl http://localhost/user/0{"timestamp":1550223186170,"status":500,"error":"Internal Server Error","exception":"java.lang.IllegalArgumentException","message":"id < 1","path":"/user/0"}
下面通過熱更新程式碼,修改這個邏輯。
jad反編譯UserController
jad --source-only com.example.demo.arthas.user.UserController > /tmp/UserController.java
jad反編譯的結果儲存在 /tmp/UserController.java
檔案裡了。
再開啟一個Terminal 3
,然後用vim來編輯/tmp/UserController.java
:
vim /tmp/UserController.java
比如當 user id 小於1時,也正常返回,不丟擲異常:
@GetMapping(value={"/user/{id}"}) public User findUserById(@PathVariable Integer id) { logger.info("id: {}", (Object)id); if (id != null && id < 1) { return new User(id, "name" + id); // throw new IllegalArgumentException("id < 1"); } return new User(id.intValue(), "name" + id); }
sc查詢載入UserController的ClassLoader
sc -d *UserController | grep classLoaderHash$ sc -d *UserController | grep classLoaderHash classLoaderHash 1be6f5c3
可以發現是 spring boot LaunchedURLClassLoader@1be6f5c3
載入的。
請記下你的classLoaderHash,後面需要使用它。在這裡,它是 1be6f5c3
。
mc
儲存好/tmp/UserController.java
之後,使用mc
(Memory Compiler)命令來編譯,並且通過-c
或者--classLoaderClass
引數指定ClassLoader:
mc --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader /tmp/UserController.java -d /tmp$ mc --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader /tmp/UserController.java -d /tmpMemory compiler output:/tmp/com/example/demo/arthas/user/UserController.classAffect(row-cnt:1) cost in 346 ms
也可以通過mc -c <classLoaderHash> /tmp/UserController.java -d /tmp
,使用-c
引數指定ClassLoaderHash:
$ mc -c 1be6f5c3 /tmp/UserController.java -d /tmp
redefine
再使用redefine
命令重新載入新編譯好的UserController.class
:
redefine /tmp/com/example/demo/arthas/user/UserController.class$ redefine /tmp/com/example/demo/arthas/user/UserController.classredefine success, size: 1
熱修改程式碼結果
redefine
成功之後,再次訪問 https://2886795301-80-host12nc.environments.katacoda.com/user/0 ,結果是:
{ "id": 0, "name": "name0"}
案例: 動態更新應用Logger Level
在這個案例裡,動態修改應用的Logger Level。
查詢UserController的ClassLoader
sc -d com.example.demo.arthas.user.UserController | grep classLoaderHash$ sc -d com.example.demo.arthas.user.UserController | grep classLoaderHash classLoaderHash 1be6f5c3
用ognl獲取logger
ognl --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader '@com.example.demo.arthas.user.UserController@logger'$ ognl --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader '@com.example.demo.arthas.user.UserController@logger'@Logger[ serialVersionUID=@Long[5454405123156820674], FQCN=@String[ch.qos.logback.classic.Logger], name=@String[com.example.demo.arthas.user.UserController], level=null, effectiveLevelInt=@Integer[20000], parent=@Logger[Logger[com.example.demo.arthas.user]], childrenList=null, aai=null, additive=@Boolean[true], loggerContext=@LoggerContext[ch.qos.logback.classic.LoggerContext[default]],]
可以知道UserController@logger
實際使用的是logback。可以看到level=null
,則說明實際最終的level是從root
logger裡來的。
單獨設定UserController的logger level
ognl --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader '@com.example.demo.arthas.user.UserController@logger.setLevel(@ch.qos.logback.classic.Level@DEBUG)'
再次獲取UserController@logger
,可以發現已經是DEBUG
了:
ognl --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader '@com.example.demo.arthas.user.UserController@logger'$ ognl --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader '@com.example.demo.arthas.user.UserController@logger'@Logger[ serialVersionUID=@Long[5454405123156820674], FQCN=@String[ch.qos.logback.classic.Logger], name=@String[com.example.demo.arthas.user.UserController], level=@Level[DEBUG], effectiveLevelInt=@Integer[10000], parent=@Logger[Logger[com.example.demo.arthas.user]], childrenList=null, aai=null, additive=@Boolean[true], loggerContext=@LoggerContext[ch.qos.logback.classic.LoggerContext[default]],]
修改logback的全域性logger level
通過獲取root
logger,可以修改全域性的logger level:
ognl --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader '@org.slf4j.LoggerFactory@getLogger("root").setLevel(@ch.qos.logback.classic.Level@DEBUG)'
案例: 排查logger衝突問題
在這個案例裡,展示排查logger衝突的方法。
確認應用使用的logger系統
以UserController
為例,它使用的是slf4j api,但實際使用到的logger系統是logback。
ognl --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader '@com.example.demo.arthas.user.UserController@logger'$ ognl --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader '@com.example.demo.arthas.user.UserController@logger'@Logger[ serialVersionUID=@Long[5454405123156820674], FQCN=@String[ch.qos.logback.classic.Logger], name=@String[com.example.demo.arthas.user.UserController], level=null, effectiveLevelInt=@Integer[20000], parent=@Logger[Logger[com.example.demo.arthas.user]], childrenList=null, aai=null, additive=@Boolean[true], loggerContext=@LoggerContext[ch.qos.logback.classic.LoggerContext[default]],]
獲取logback實際載入的配置檔案
ognl --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader '#map1=@org.slf4j.LoggerFactory@getLogger("root").loggerContext.objectMap, #map1.get("CONFIGURATION_WATCH_LIST")'
使用classloader命令查詢可能存在的logger配置檔案
classloader --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader -r logback-spring.xml$ classloader --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader -r logback-spring.xml jar:file:/Users/hengyunabc/code/java/spring-boot-inside/demo-arthas-spring-boot/target/demo-arthas-spring-boot-0.0.1-SNAPSHOT.jar!/BOOT-INF/classes!/logback-spring.xmlAffect(row-cnt:1) cost in 13 ms.
可以知道載入的配置的具體來源。
可以嘗試載入容易衝突的檔案:
classloader --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader -r logback.xmlclassloader --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader -r log4j.properties
案例: 獲取Spring Context
在這個案例裡,展示獲取spring context,再獲取bean,然後呼叫函式。
使用tt命令獲取到spring context
tt
即 TimeTunnel,它可以記錄下指定方法每次呼叫的入參和返回資訊,並能對這些不同的時間下呼叫進行觀測。
tt -t org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter invokeHandlerMethod
訪問:https://2886795309-80-host09nc.environments.katacoda.com/user/1
可以看到tt
命令捕獲到了一個請求:
$ tt -t org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdaptePress Q or Ctrl+C to abort.Affect(class-cnt:1 , method-cnt:1) cost in 252 ms. INDE TIMESTAMP COST( IS-R IS- OBJECT CLASS METHOD X ms) ET EXP----------------------------------------------------------------------------------------- 1000 2019-02-15 4.583 true fal 0xc93cf1a RequestMappingHand invokeHandlerMethod 15:38:32 923 se lerAdapter
使用tt命令從呼叫記錄裡獲取到spring context
輸入 Q
或者 Ctrl + C
退出上面的 tt -t
命令。
tt -i 1000 -w 'target.getApplicationContext()'$ tt -i 1000 -w 'target.getApplicationContext()'@AnnotationConfigEmbeddedWebApplicationContext[ reader=@AnnotatedBeanDefinitionReader[org.springframework.context.annotation.AnnotatedBeanDefinitionReader@2e457641], scanner=@ClassPathBeanDefinitionScanner[org.springframework.context.annotation.ClassPathBeanDefinitionScanner@6eb38026], annotatedClasses=null, basePackages=null,]Affect(row-cnt:1) cost in 439 ms.
獲取spring bean,並呼叫函式
tt -i 1000 -w 'target.getApplicationContext().getBean("helloWorldService").getHelloMessage()'
結果是:
$ tt -i 1000 -w 'target.getApplicationContext().getBean("helloWorldService").getHelloMessage()'@String[Hello World]Affect(row-cnt:1) cost in 52 ms.
案例: 排查HTTP請求返回401
在這個案例裡,展示排查HTTP 401問題的技巧。
訪問: https://2886795309-80-host09nc.environments.katacoda.com/admin
結果是:
Something went wrong: 401 Unauthorized
我們知道401
通常是被許可權管理的Filter
攔截了,那麼到底是哪個Filter
處理了這個請求,返回了401?
跟蹤所有的Filter函式
開始trace:
trace javax.servlet.Filter *
訪問: https://2886795309-80-host09nc.environments.katacoda.com/admin
可以在呼叫樹的最深層,找到AdminFilterConfig$AdminFilter
返回了401
:
+---[3.806273ms] javax.servlet.FilterChain:doFilter()| `---[3.447472ms] com.example.demo.arthas.AdminFilterConfig$AdminFilter:doFilter()| `---[0.17259ms] javax.servlet.http.HttpServletResponse:sendError()
通過stack獲取呼叫棧
上面是通過trace
命令來獲取資訊,從結果裡,我們可以知道通過stack
跟蹤HttpServletResponse:sendError()
,同樣可以知道是哪個Filter
返回了401
執行:
stack javax.servlet.http.HttpServletResponse sendError 'params[0]==401'
訪問: https://2886795309-80-host09nc.environments.katacoda.com/admin
$ stack javax.servlet.http.HttpServletResponse sendError 'params[0]==401'Press Q or Ctrl+C to abort.Affect(class-cnt:2 , method-cnt:4) cost in 87 ms.ts=2019-02-15 16:44:06;thread_name=http-nio-8080-exec-6;id=16;is_daemon=true;priority=5;TCCL=org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedWebappClassLoader@8546cd5 @org.apache.catalina.connector.ResponseFacade.sendError() at com.example.demo.arthas.AdminFilterConfig$AdminFilter.doFilter(AdminFilterConfig.java:38) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
案例: 排查HTTP請求返回404
在這個案例裡,展示排查HTTP 404問題的技巧。
訪問: https://2886795309-80-host09nc.environments.katacoda.com/a.txt
結果是:
Something went wrong: 404 Not Found
那麼到底是哪個Servlet處理了這個請求,返回了404?
跟蹤所有的Servlet函式
開始trace:
trace javax.servlet.Servlet * > /tmp/servlet.txt
訪問: https://2886795309-80-host09nc.environments.katacoda.com/a.txt
在Terminal 3
裡,檢視/tmp/servlet.txt
的內容:
less /tmp/servlet.txt
/tmp/servlet.txt
裡的內容會比較多,需要耐心找到呼叫樹裡最長的地方。
可以發現請求最終是被freemarker
處理的:
`---[13.974188ms] org.springframework.web.servlet.ViewResolver:resolveViewName() +---[0.045561ms] javax.servlet.GenericServlet:<init>() +---[min=0.045545ms,max=0.074342ms,total=0.119887ms,count=2] org.springframework.web.servlet.view.freemarker.FreeMarkerView$GenericServletAdapter:<init>() +---[0.170895ms] javax.servlet.GenericServlet:init() | `---[0.068578ms] javax.servlet.GenericServlet:init() | `---[0.021793ms] javax.servlet.GenericServlet:init() `---[0.164035ms] javax.servlet.GenericServlet:getServletContext()
案例: 理解Spring Boot應用的ClassLoader結構
下面介紹classloader
命令的功能。
先訪問一個jsp網頁,觸發jsp的載入: https://2886795309-80-host09nc.environments.katacoda.com/hello
列出所有ClassLoader
classloader -l$ classloader -l name loadedCount hash parent BootstrapClassLoader 2724 null null com.taobao.arthas.agent.ArthasClassloader@411ce1ab 2009 411ce1ab sun.misc.Launcher$ExtClassLoader@7494e528 com.taobao.arthas.agent.ArthasClassloader@22ae1234 1253 22ae1234 sun.misc.Launcher$ExtClassLoader@7494e528 org.apache.jasper.servlet.JasperLoader@65361d9a 1 65361d9a TomcatEmbeddedWebappClassLoader context: ROOT delegate: true ----------> Parent Classloader: org.springframework.boot.loader.LaunchedURLClassLoader@1be6f5c3 TomcatEmbeddedWebappClassLoader 0 8546cd5 org.springframework.boot.loader.LaunchedURLClassLoader@1be6f5c3 context: ROOT delegate: true ----------> Parent Classloader: org.springframework.boot.loader.LaunchedURLClassLoader@1be6f5c3 org.springframework.boot.loader.LaunchedURLClassLoader@1be6f5c3 5416 1be6f5c3 sun.misc.Launcher$AppClassLoader@3d4eac69 sun.misc.Launcher$AppClassLoader@3d4eac69 45 3d4eac69 sun.misc.Launcher$ExtClassLoader@7494e528 sun.misc.Launcher$ExtClassLoader@7494e528 4 7494e528 null
- TomcatEmbeddedWebappClassLoader 載入的class數量是0,所以在spring boot embedded tomcat裡,它只是一個空殼,所有的類載入都是
LaunchedURLClassLoader
完成的
列出ClassLoader里載入的所有類
列出上面的org.apache.jasper.servlet.JasperLoader
載入的類:
classloader -a --classLoaderClass org.apache.jasper.servlet.JasperLoader$ classloader -a --classLoaderClass apache.jasper.servlet.JasperLoader hash:1698045338, org.apache.jasper.servlet.JasperLoader@65361d9a org.apache.jsp.jsp.hello_jsp
- 注:同ognl, 也可用
-c <hashcode>
而不用--classLoaderClass
指定
反編譯jsp的程式碼
jad org.apache.jsp.jsp.hello_jsp$ jad org.apache.jsp.jsp.hello_jspClassLoader:+-org.apache.jasper.servlet.JasperLoader@65361d9a +-TomcatEmbeddedWebappClassLoader context: ROOT...
檢視ClassLoader樹
classloader -t$ classloader -t+-BootstrapClassLoader+-sun.misc.Launcher$ExtClassLoader@28cbbddd +-com.taobao.arthas.agent.ArthasClassloader@8c25e55 +-sun.misc.Launcher$AppClassLoader@55f96302 +-org.springframework.boot.loader.LaunchedURLClassLoader@1be6f5c3 +-TomcatEmbeddedWebappClassLoader context: ROOT delegate: true ----------> Parent Classloader: org.springframework.boot.loader.LaunchedURLClassLoader@1be6f5c3 +-org.apache.jasper.servlet.JasperLoader@21ae0fe2
注意:請使用你的classLoaderHash值覆蓋 <classLoaderHash>
,然後手動執行下面相關命令:
列出ClassLoader的urls
比如上面檢視到的spring LaunchedURLClassLoader的 hashcode是1be6f5c3
,可以通過-c
或者--classLoaderClass
引數來列出它的所有urls:
classloader --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader$ classloader --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoaderjar:file:/home/scrapbook/tutorial/demo-arthas-spring-boot.jar!/BOOT-INF/classes!/jar:file:/home/scrapbook/tutorial/demo-arthas-spring-boot.jar!/BOOT-INF/lib/spring-boot-starter-aop-1.5.13.RELEASE.jar!/...
載入指定ClassLoader裡的資原始檔
查詢指定的資原始檔: classloader --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader -r logback-spring.xml
$ classloader --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader -r logback-spring.xml jar:file:/home/scrapbook/tutorial/demo-arthas-spring-boot.jar!/BOOT-INF/classes!/logback-spring.xml
嘗試載入指定的類
比如用上面的spring LaunchedURLClassLoader 嘗試載入 java.lang.String
:
classloader --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader --load java.lang.String$ classloader --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader --load java.lang.Stringload class success. class-info java.lang.String code-source name java.lang.String isInterface false isAnnotation false isEnum false isAnonymousClass false isArray false isLocalClass false isMemberClass false isPrimitive false isSynthetic false simple-name String modifier final,public annotation interfaces java.io.Serializable,java.lang.Comparable,java.lang.CharSequence super-class +-java.lang.Object class-loader classLoaderHash null
案例:查詢Top N執行緒
檢視所有執行緒資訊
thread
檢視具體執行緒的棧
檢視執行緒ID 16的棧:
thread 16
檢視CPU使用率top n執行緒的棧
thread -n 3
檢視5秒內的CPU使用率top n執行緒棧
thread -n 3 -i 5000
查詢執行緒是否有阻塞
thread -b
Web Console
Arthas支援通過Web Socket來連線。
教程裡的Web Console
注意:教程裡訪問的是80埠,因為做了埠轉發。在本地體驗時,需要訪問8563埠。
本地體驗
當在本地啟動時,可以訪問 http://127.0.0.1:8563/ ,通過瀏覽器來使用Arthas。
推薦通過“快速入門”來體驗: https://arthas.aliyun.com/doc/quick-start.html
Exit/Stop
reset
Arthas在 watch/trace 等命令時,實際上是修改了應用的位元組碼,插入增強的程式碼。顯式執行 reset
命令,可以清除掉這些增強程式碼。
退出Arthas
用 exit
或者 quit
命令可以退出Arthas。
退出Arthas之後,還可以再次用 java -jar arthas-boot.jar
來連線。
徹底退出Arthas
exit/quit
命令只是退出當前session,arthas server還在目標程式中執行。
想完全退出Arthas,可以執行 stop
命令。
Arthas-boot支援的引數
arthas-boot.jar
支援很多引數,可以執行 java -jar arthas-boot.jar -h
來檢視。
允許外部訪問
預設情況下, arthas server偵聽的是 127.0.0.1
這個IP,如果希望遠端可以訪問,可以使用--target-ip
的引數。
java -jar arthas-boot.jar --target-ip
列出所有的版本
java -jar arthas-boot.jar --versions
使用指定版本:
java -jar arthas-boot.jar --use-version 3.1.0
只偵聽Telnet埠,不偵聽HTTP埠
java -jar arthas-boot.jar --telnet-port 9999 --http-port -1
列印執行的詳情
java -jar arthas-boot.jar -v