Arthas
是Alibaba开源的Java诊断工具,深受开发者喜爱。
Arthas能解决什么
当你遇到以下类似问题而束手无策时,
Arthas
可以帮助你解决:
- 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
- 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
- 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
- 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
- 是否有一个全局视角来查看系统的运行状况?
- 有什么办法可以监控到JVM的实时运行状态?
Arthas
支持JDK 6+,支持Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 Tab
自动补全功能,进一步方便进行问题的定位和诊断。
Arthas
在线教程(推荐)
Arthas
安装
使用arthas-boot
(推荐)
- 下载
arthas-boot.jar
,然后用java -jar
的方式启动:
wget https://alibaba.github.io/arthas/arthas-boot.jar
java -jar arthas-boot.jar
- 打印帮助信息:
java -jar arthas-boot.jar -h
- 如果下载速度比较慢,可以使用aliyun的镜像:
java -jar arthas-boot.jar --repo-mirror aliyun --use-http
使用as.sh
Arthas 支持在 Linux/Unix/Mac 等平台上一键安装,请复制以下内容,并粘贴到命令行中,敲 回车
执行即可:
curl -L https://alibaba.github.io/arthas/install.sh | sh
上述命令会下载启动脚本文件 as.sh
到当前目录,你可以放在任何地方或将其加入到 $PATH
中。
直接在shell下面执行./as.sh
,就会进入交互界面。
也可以执行./as.sh -h
来获取更多参数信息。
Arthas
常用命令
Dashboard
当前系统的实时数据面板
参数解释
ID: Java级别的线程ID,注意这个ID不能跟jstack中的nativeID一一对应
NAME: 线程名
GROUP: 线程组名
PRIORITY: 线程优先级, 1~10之间的数字,越大表示优先级越高
STATE: 线程的状态
CPU%: 线程消耗的cpu占比,采样100ms,将所有线程在这100ms内的cpu使用量求和,再算出每个线程的cpu使用占比。
TIME: 线程运行总时间
INTERRUPTE: 线程当前的中断位状态
DAEMON: 是否是daemon线程
Thread
查看当前 JVM 的线程堆栈信息
一目了然的了解系统的状态,哪些线程比较占cpu?他们到底在做什么?
$ thread -n 3 "as-command-execute-daemon" Id=29 cpuUsage=75% RUNNABLE at sun.management.ThreadImpl.dumpThreads0(Native Method) at sun.management.ThreadImpl.getThreadInfo(ThreadImpl.java:440) at com.taobao.arthas.core.command.monitor200.ThreadCommand$1.action(ThreadCommand.java:58) at com.taobao.arthas.core.command.handler.AbstractCommandHandler.execute(AbstractCommandHandler.java:238) at com.taobao.arthas.core.command.handler.DefaultCommandHandler.handleCommand(DefaultCommandHandler.java:67) at com.taobao.arthas.core.server.ArthasServer$4.run(ArthasServer.java:276) 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@6cd0b6f8 "as-session-expire-daemon" Id=25 cpuUsage=24% TIMED_WAITING at java.lang.Thread.sleep(Native Method) at com.taobao.arthas.core.server.DefaultSessionManager$2.run(DefaultSessionManager.java:85) "Reference Handler" Id=2 cpuUsage=0% WAITING on java.lang.ref.Reference$Lock@69ba0f27 at java.lang.Object.wait(Native Method) - waiting on java.lang.ref.Reference$Lock@69ba0f27 at java.lang.Object.wait(Object.java:503) at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:133)
参数说明
id
线程id
-n
指定最忙的前N个线程并打印堆栈
b
找出当前阻塞其他线程的线程
-i
指定cpu占比统计的采样间隔,单位为毫秒
PS: 这里的cpu统计的是,一段采样间隔内,当前JVM里各个线程所占用的cpu时间占总cpu时间的百分比。其计算方法为: 首先进行一次采样,获得所有线程的cpu的使用时间(调用的是java.lang.management.ThreadMXBean#getThreadCpuTime这个接口),然后睡眠一段时间,默认100ms,可以通过-i参数指定,然后再采样一次,最后得出这段时间内各个线程消耗的cpu时间情况,最后算出百分比。注意: 这个统计也会产生一定的开销(JDK这个接口本身开销比较大),因此会看到as的线程占用一定的百分比,为了降低统计自身的开销带来的影响,可以把采样间隔拉长一些,比如5000毫秒。
jad
对类进行反编译:
$ jad javax.servlet.Servlet ClassLoader: +-java.net.URLClassLoader@6108b2d7 +-sun.misc.Launcher$AppClassLoader@18b4aac2 +-sun.misc.Launcher$ExtClassLoader@1ddf84b8 Location: /Users/xxx/work/test/lib/servlet-api.jar /* * Decompiled with CFR 0_122. */ package javax.servlet; import java.io.IOException; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; public interface Servlet { public void init(ServletConfig var1) throws ServletException; public ServletConfig getServletConfig(); public void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException; public String getServletInfo(); public void destroy(); }
mc
Memory Compiler/内存编译器,编译
.java
文件生成.class
。
mc /tmp/Test.java
redefine
加载外部的
.class
文件,redefine jvm已加载的类。
redefine /tmp/Test.class redefine -c 327a647b /tmp/Test.class /tmp/Test\$Inner.class
sc
查找JVM中已经加载的类
$ sc -d org.springframework.web.context.support.XmlWebApplicationContext class-info org.springframework.web.context.support.XmlWebApplicationContext code-source /Users/xxx/work/test/WEB-INF/lib/spring-web-3.2.11.RELEASE.jar name org.springframework.web.context.support.XmlWebApplicationContext isInterface false isAnnotation false isEnum false isAnonymousClass false isArray false isLocalClass false isMemberClass false isPrimitive false isSynthetic false simple-name XmlWebApplicationContext modifier public annotation interfaces super-class +-org.springframework.web.context.support.AbstractRefreshableWebApplicationContext +-org.springframework.context.support.AbstractRefreshableConfigApplicationContext +-org.springframework.context.support.AbstractRefreshableApplicationContext +-org.springframework.context.support.AbstractApplicationContext +-org.springframework.core.io.DefaultResourceLoader +-java.lang.Object class-loader +-org.apache.catalina.loader.ParallelWebappClassLoader +-java.net.URLClassLoader@6108b2d7 +-sun.misc.Launcher$AppClassLoader@18b4aac2 +-sun.misc.Launcher$ExtClassLoader@1ddf84b8 classLoaderHash 25131501
参数说明
class-pattern
类名表达式匹配
-d
输出当前类的详细信息,包括这个类所加载的原始文件来源、类的声明、加载的ClassLoader等详细信息。如果一个类被多个ClassLoader所加载,则会出现多次
-E
开启正则表达式匹配,默认为通配符匹配
-f
输出当前类的成员变量信息(需要配合参数-d一起使用)
-x
指定输出静态变量时属性的遍历深度,默认为 0,即直接使用 toString 输出
PS: class-pattern支持全限定名,如com.test.AAA,也支持com/test/AAA这样的格式,这样,我们从异常堆栈里面把类名拷贝过来的时候,不需要在手动把/替换为.啦,
sc 默认开启了子类匹配功能,也就是说所有当前类的子类也会被搜索出来,想要精确的匹配,请打开options disable-sub-class true开关
sm
参数说明
class-pattern
类名表达式匹配
method-pattern
方法名表达式匹配
-d
展示每个方法的详细信息
-E
开启正则表达式匹配,默认为通配符匹配
PS:查看已加载类的方法信息, “Search-Method” 的简写,这个命令能搜索出所有已经加载了 Class 信息的方法信息。
sm 命令只能看到由当前类所声明 (declaring) 的方法,父类则无法看到
stack
查看方法
test.arthas.TestStack#doGet
的调用堆栈:
$ stack test.arthas.TestStack doGet Press Ctrl+C to abort. Affect(class-cnt:1 , method-cnt:1) cost in 286 ms. ts=2018-09-18 10:11:45;thread_name=http-bio-8080-exec-10;id=d9;is_daemon=true;priority=5;TCCL=org.apache.catalina.loader.ParallelWebappClassLoader@25131501 @test.arthas.TestStack.doGet() at javax.servlet.http.HttpServlet.service(HttpServlet.java:624) at javax.servlet.http.HttpServlet.service(HttpServlet.java:731) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:110) ... at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:169) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:451) at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1121) at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:637) at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:316) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:745)
Trace
方法内部调用路径,并输出方法路径上的每个节点上耗时, trace 命令能主动搜索 class-pattern/method-pattern 对应的方法调用路径,渲染和统计整个调用链路上的所有性能开销和追踪调用链路。
trace 能方便的帮助你定位和发现因 RT 高而导致的性能问题缺陷,但其每次只能跟踪一级方法的调用链路
trace 在执行的过程中本身是会有一定的性能开销,在统计的报告中并未像 JProfiler 一样预先减去其自身的统计开销。
所以这统计出来有些许的不准,渲染路径上调用的类、方法越多,性能偏差越大。但还是能让你看清一些事情的。
- 观察方法执行的时候哪个子调用比较慢:
参数说明
class-pattern
类名表达式匹配
method-pattern
方法名表达式匹配
condition-express
条件表达式
-n
命令执行次数
#cost
方法执行耗时
PS: 很多时候我们只想看到某个方法的rt大于某个时间之后的trace结果,例如trace *StringUtils isBlank '$cost>100'表示当执行时间超过100ms的时候,才会输出trace的结果。
Watch
方法执行数据观测, 让你能方便的观察到指定方法的调用情况。
能观察到的范围为:返回值、抛出异常、入参,通过编写 groovy 表达式进行对应变量的查看。
- 观察方法
test.arthas.TestWatch#doGet
执行的入参,仅当方法抛出异常时才输出。
$ watch test.arthas.TestWatch doGet {params[0], throwExp} -e Press Ctrl+C to abort. Affect(class-cnt:1 , method-cnt:1) cost in 65 ms. ts=2018-09-18 10:26:28;result=@ArrayList[ @RequestFacade[org.apache.catalina.connector.RequestFacade@79f922b2], @NullPointerException[java.lang.NullPointerException], ]
参数说明
class-pattern
类名表达式匹配
method-pattern
方法名表达式匹配
express
观察表达式
condition-express
条件表达式
-b
在方法调用之前观察(默认关闭)
-e
在方法异常之后观察(默认关闭)
-s
在方法返回之后观察(默认关闭)
-f
在方法结束之后(正常返回和异常返回)观察 (默认开启)
-x
指定输出结果的属性遍历深度,默认为0
PS:这里重点要说明的是观察表达式,观察表达式的构成主要由 groovy 表达式组成,只要是一个合法的 groovy 表达式,都能被正常支持。
观察的维度也比较多,主要体现在参数 advice 的数据结构上。Advice 参数最主要是封装了通知节点的所有信息。
Monitor
monitor 命令是一个非实时返回命令,实时返回命令是输入之后立即返回,而非实时返回的命令,则是不断的等待目标 Java 进程返回信息,直到用户输入 Ctrl+C 为止。
服务端是以任务的形式在后台跑任务,植入的代码随着任务的中止而被不会被执行,所以任务关闭后,不会对原有性能产生太大影响,而且原则上,任何 Arthas 的命令也不会引起任何原有业务逻辑的改变
- 监控某个特殊方法的调用统计数据,包括总调用次数,平均rt,成功率等信息,每隔5秒输出一次。
$ monitor -c 5 org.apache.dubbo.demo.provider.DemoServiceImpl sayHello Press Ctrl+C to abort. Affect(class-cnt:1 , method-cnt:1) cost in 109 ms. timestamp class method total success fail avg-rt(ms) fail-rate ---------------------------------------------------------------------------------------------------------------------------- 2018-09-20 09:45:32 org.apache.dubbo.demo.provider.DemoServiceImpl sayHello 5 5 0 0.67 0.00% timestamp class method total success fail avg-rt(ms) fail-rate ---------------------------------------------------------------------------------------------------------------------------- 2018-09-20 09:45:37 org.apache.dubbo.demo.provider.DemoServiceImpl sayHello 5 5 0 1.00 0.00% timestamp class method total success fail avg-rt(ms) fail-rate ---------------------------------------------------------------------------------------------------------------------------- 2018-09-20 09:45:42 org.apache.dubbo.demo.provider.DemoServiceImpl sayHello 5 5 0 0.43 0.00%
参数说明
class-pattern
类名表达式匹配
method-pattern
方法名表达式匹配
-c
统计周期,默认值为120秒
监控项
timestamp 时间戳
class java类
method 方法(构造方法、普通方法)
total 调用次数
success 成功次数
fail 失败次数
rt 平均rt
fail-rate 失败率
PS:方法执行监控, 对匹配 class-pattern/method-pattern的类、方法的调用进行监控。
Time Tunnel(tt)
记录方法调用信息,支持事后查看方法调用的参数,返回值,抛出的异常等信息,仿佛穿越时空隧道回到调用现场一般。
$ tt -t org.apache.dubbo.demo.provider.DemoServiceImpl sayHello Press Ctrl+C to abort. Affect(class-cnt:1 , method-cnt:1) cost in 75 ms. INDEX TIMESTAMP COST(ms) IS-RET IS-EXP OBJECT CLASS METHOD ------------------------------------------------------------------------------------------------------------------------------------- 1000 2018-09-20 09:54:10 1.971195 true false 0x55965cca DemoServiceImpl sayHello 1001 2018-09-20 09:54:11 0.215685 true false 0x55965cca DemoServiceImpl sayHello 1002 2018-09-20 09:54:12 0.236303 true false 0x55965cca DemoServiceImpl sayHello 1003 2018-09-20 09:54:13 0.159598 true false 0x55965cca DemoServiceImpl sayHello 1004 2018-09-20 09:54:14 0.201982 true false 0x55965cca DemoServiceImpl sayHello 1005 2018-09-20 09:54:15 0.214205 true false 0x55965cca DemoServiceImpl sayHello 1006 2018-09-20 09:54:16 0.241863 true false 0x55965cca DemoServiceImpl sayHello 1007 2018-09-20 09:54:17 0.305747 true false 0x55965cca DemoServiceImpl sayHello 1008 2018-09-20 09:54:18 0.18468 true false 0x55965cca DemoServiceImpl sayHello
Classloader
了解当前系统中有多少类加载器,以及每个加载器加载的类数量,帮助您判断是否有类加载器泄露。
$ classloader name numberOfInstances loadedCountTotal BootstrapClassLoader 1 3346 com.taobao.arthas.agent.ArthasClassloader 1 1262 java.net.URLClassLoader 2 1033 org.apache.catalina.loader.ParallelWebappClassLoader 1 628 sun.reflect.DelegatingClassLoader 166 166 sun.misc.Launcher$AppClassLoader 1 31 com.alibaba.fastjson.util.ASMClassLoader 6 15 sun.misc.Launcher$ExtClassLoader 1 7 org.jvnet.hk2.internal.DelegatingClassLoader 2 2 sun.reflect.misc.MethodUtil 1 1
Web Console