Java诊断工具Arthas
概述
- 官网:https://arthas.aliyun.com/en-us/
- github:https://github.com/alibaba/arthas
- Arthas是Alibaba开源的Java诊断工具,支持JDK 6+,支持Linux/Mac/Windows,采用命令行交互模式,提供丰富的tab补全功能,是排查jvm相关问题的利器
- 使用时可以使用
help
查看所有命令,多使用-h
查看命令使用帮助,使用tab
进行命令补全
安装、卸载、启动
- 从Maven仓库下载全量包:
arthas-packaging-3.5.4-bin.zip
,解压到指定目录即可使用 - 卸载:安装后home目录有两个arthas目录,
~/.arthas
和~/log/arthas
,卸载时删除这两个目录即可 - 启动:
java -jar arthas/arthas-boot.jar
,选择目标进程进程进行attach- 必须有java进程才能启动,可以启动自带的demo程序,
java -jar arthas/math-game.jar
,源码 - 启动后选择指定的java进程即可attach成功,attach后自动telnet 127.0.0.1 3658
- 如果端口冲突可以指定端口执行arthas,
--telnet-port
配置0或-1必须在配置文件里面配,--http-port
都可以,命令行优先 - 也可以在浏览器访问,telnet端口和http端口都可以访问,执行arthas命令(linux上启动后无法在windows浏览器访问,因为windows上启动可以访问,默认只监听127.0.0.1,可以指定参数
--target-ip ip
,然后web端就可以访问了)
- 必须有java进程才能启动,可以启动自带的demo程序,
快速入门
dashboard
:展示当前进程的信息,默认每5s更新一次,退出q
或Ctrl + C
thread
:显示当前进程所有的线程,查看某个线程的详细信息,thread ID
jad
:反编译指定的类或方法,如jad demo.MathGame
watch
:监视,如监控某个方法的返回值:watch demo.MathGame method returnObj
- 其他命令
- 退出arthas:
q
或quit
或exit
,此时attach到目标进程上的arthas还在继续运行,端口会保持开放,下次可以直接连上。 - 完全退出:
stop
tab
自动补全(任何时候都可以使用,以提示相关信息),上箭头显示上次执行的命令,类似于linux
- 退出arthas:
基础命令
help
:查看命令帮助信息,某个命令的帮助信息加-h
或--help
,或按tab
自动补全cat
,echo
,grep
(只能用于管道),base64
,tee
,pwd
,wc -l
,与linux里的类似cls
:清屏,linux:clear,windows:cls,mobaxterm使用Ctrl + L
session
:显示当前会话信息,使用tunnel server时查看agentIdreset
:重置增强类,将被 Arthas 增强过的类全部还原,Arthas 服务端关闭时会重置所有增强过的类。- 还原指定类、还原指定类的指定方法、还原正则匹配的类或方法,详见
--help
或tab
- 还原指定类、还原指定类的指定方法、还原正则匹配的类或方法,详见
version
:显示arthas版本号history
:打印命令历史quit
:退出当前 Arthas 客户端,其他 Arthas 客户端不受影响,stop
:关闭arthas服务端,所有客户端全部退出keymap
:Arthas快捷键列表及自定义快捷键
jvm相关
dashboard
:当前系统的实时数据面板,目标jvm的线程、内存、gc、vm、tomcat信息- 指定执行间隔(默认5s),指定执行次数
thread
:查看当前线程信息,查看线程的堆栈。当没有参数时,显示第一页线程的信息。其他查看java进程cpu使用率方法:https://github.com/oldratlee/useful-scripts/blob/master/docs/java.md#-show-busy-java-threads
thread -n 3 # 一键列出最忙的3个线程并打印堆栈
thread –all # 显示所有匹配的线程
thread id # 显示指定线程的运行堆栈,id为thread命令显示的线程id
thread -b # 找出当前阻塞其他线程的线程
thread -i 1000 # 指定采样时间间隔,单位ms
thread –state WAITING # 查看指定状态的线程,
jvm
:查看当前JVM信息sysprop
:查看、修改当前jvm的系统属性,查看单个属性sysprop java.home
,修改单个属性sysprop java.home XXX
sysenv
:查看当前JVM的环境变量,查看单个属性sysenv PATH
vmoption
:查看、更新VM诊断相关的参数,使用同sysprop
perfcounter
:查看当前JVM的 Perf Counter信息logger
:查看和修改loggergetstatic
:通过getstatic命令可以方便的查看类的静态属性。使用方法为getstatic class_name field_name。-x 2
指定展开层级,默认1。推荐直接使用ognl命令,更加灵活ognl
:对象图导航语言,参考:https://arthas.aliyun.com/doc/ognl.html
ognl常用对象
SUMMARY:
Display the input/output parameter, return object, and thrown exception of specified method invocation
The express may be one of the following expression (evaluated dynamically):
target : the object
clazz : the object's class
method : the constructor or method
params : the parameters array of method
params[0..n] : the element of parameters array
returnObj : the returned object of method
throwExp : the throw exception of method
isReturn : the method ended by return
isThrow : the method ended by throwing exception
#cost : the execution time in ms of method invocation
mbean
:查看 Mbean 的信息heapdump
:dump java heap, 类似jmap命令的heap dump功能。把java进程的堆dump到一个.hprof文件。
heapdump # dump到临时目录
heapdump /tmp/dump.hprof # dump到指定文件
heapdump --live /tmp/dump.hprof # 只dump live对象
vmtool
:vmtool 利用JVMTI接口,实现查询内存对象,强制GC等功能。-a参数执行--help
vmtool --action forceGc # 强制GC,可以结合 vmoption 命令动态打开PrintGC开关,查看GC信息
vmtool -a getInstances --className java.lang.String --limit 10 # 获取对象
class/classloader相关
指定类加载器使用--classLoaderClass <value>
或 -c <hashvalue>
配置,类加载器可以通过sc -d
搜索类查看,也可以使用classloader
命令
sc
:查看JVM已加载的类信息,“Search-Class” 的简写,这个命令能搜索出所有已经加载到 JVM 中的 Class 信息。【可以指定classloader】
sc demo.* # 模糊搜索
sc -d demo.MathGame # 打印类的详细信息
sc -d -f demo.MathGame # 打印出类的Field信息
sm
:查看已加载类的方法信息,“Search-Method” 的简写,这个命令能搜索出所有已经加载了 Class 信息的方法信息。sm 命令只能看到由当前类所声明 (declaring) 的方法,父类则无法看到。【可以指定classloader】
sm java.lang.String # 查看类的所有方法
sm -d java.lang.String toString #详细查看某个方法,或详细查看某个类的所有方法(不加方法)
jad
:反编译指定已加载类的源码,将 JVM 中实际运行的 class 的 byte code 反编译成 java 代码,便于你理解业务逻辑。【可以指定classloader】
jad java.lang.String
jad --source-only demo.MathGame # 只显示源码,不显示classloader location
jad demo.MathGame main --source-only # 反编译指定方法
jad demo.MathGame main --source-only --lineNumber false # 不显示行号
mc
:Memory Compiler/内存编译器,编译.java文件生成.class。【可以指定classloader】
mc sourceA.java
mc -d outputDir sourceA.java sourceB.java # 输出到指定目录
retransform
:加载外部的.class文件,retransform jvm已加载的类。如果要删除影响,需要删除所有的entry,重新retransform,arthas stop时,retransform过的类依然生效。上传.class技巧,有写服务器不允许直接上传文件:base64 < Test.class > result.txt
,base64 -d < result.txt > Test.class
,再使用md5sum计算哈希值,校验是否一致
retransform /tmp/MathGame.class # 替换已加载的类
retransform -l # 列出所有的retransform
retransform -d 1 # 按编号删除
retransform --deleteAll # 删除所有
retransform --classPattern demo.* # retransform指定模式的.class
- 注意:不允许新增方法和字段,正在跑的函数没有退出不能生效
redefine
:加载外部的.class文件,redefine到JVM里。推荐使用 retransform 命令,- redefine的class不能修改、添加、删除类的field和method,包括方法参数、方法名称及返回值
- 目前redefine 和watch/trace/jad/tt等命令冲突,以后重新实现redefine功能会解决此问题
redefine SourceA.class
redefine -c 327a647b SourceA.class SourceB.class
redefine --classLoaderClass sun.misc.Launcher$AppClassLoader SourceA.class SourceB.class
注意, redefine后的原来的类不能恢复,redefine有可能失败(比如增加了新的field),
reset命令对redefine的类无效。如果想重置,需要redefine原始的字节码。
redefine命令和jad/watch/trace/monitor/tt等命令会冲突。执行完redefine之后,如果再执行上面提到的命令,则会把redefine的字节码重置。 原因是jdk本身redefine和Retransform是不同的机制,同时使用两种机制来更新字节码,只有最后修改的会生效。
dump
:将已加载类的字节码文件保存到特定目录,默认为logs/arthas/classdump/
dump java.lang.String
dump demo.*
dump -d /tmp/output java.lang.String
classloader
:查看classloader的继承树,urls,类加载信息。可以让指定的classloader去getResources,打印出所有查找到的resources的url。对于ResourceNotFoundException比较有用。
classloader # 按类加载类型查看统计信息
classloader -l # 按类加载实例查看统计信息
classloader -t # 查看ClassLoader的继承树
classloader -c 39e82674 # 查看URLClassLoader实际的urls
classloader -c 39e82674 -r META-INF/MANIFEST.MF # 使用ClassLoader去查找resource
classloader -c 3d4eac69 --load demo.MathGame # 使用ClassLoader去加载类
monitor/watch/trace相关
monitor
:监控指定类中方法的执行情况统计,包括统计执行次数、成功次数、失败次数、平均执行时间、失败率,统计周期,默认60s。
monitor class-pattern method-pattern <condition-express>
monitor -c 5 demo.MathGame primeFactors # 每5s统计一次,默认60s
monitor -c 5 demo.MathGame primeFactors "params[0] <= 2" # 在方法执行后使用条件表达式过滤统计结果,第一个参数小于等于2的统计
monitor -c 1 demo.MathGame primeFactors "#cost>0.2" # 方法执行时间大于0.2ms
monitor -b -c 1 demo.MathGame primeFactors "params[0] <= 2" # 在方法执行前执行条件表达式过滤统计结果
watch
:观察到指定方法的调用情况-数据观测,包括返回值、抛出异常、入参,通过编写ognl表达式进行对应变量的查看。- 没有输出结果有两种可能:匹配的方法没有执行或条件表达式为false,可以使用-v查看条件表达式的具体值确认
watch class-pattern method-pattern <express> <condition-express>
watch demo.MathGame primeFactors # 在方法执行后观察方法执行情况,观察表达式,默认值是{params, target, returnObj}。参数、当前对象、返回值
watch demo.MathGame primeFactors -x 2 # -x表示遍历深度,默认1执行toString
watch demo.MathGame primeFactors "{params,returnObj}" -x 2 -b #在方法之前执行,因此返回值一直为null
watch demo.MathGame primeFactors "{params,target,returnObj}" -x 2 -b -s -n 2 #同时观察执行前和执行后,-n 2表示观察执行2次
watch demo.MathGame primeFactors "{params[0],target}" "params[0]<0" # 使用条件表达式,满足的才响应,'#cost>200'耗时分析
watch demo.MathGame primeFactors "{params[0],throwExp}" -e -x 2 # 观察异常-e表示在有异常时才响应
watch demo.MathGame * '{params,@demo.MathGame@random.nextInt(100)}' -v -n 1 -x 2 # 调用静态字段、静态方法
trace
:追踪方法内部调用的路径,找出该方法调用了哪些方法,输出方法路径上的每个节点的耗时。(本身有性能开销,方法越多开销越大,但还是能追踪问题的)
trace demo.MathGame run # 追踪该方法调用的路径,-n 1指定捕捉次数,--skipJDKMethod false可以追踪jdk的方法
trace demo.MathGame run '#cost > 10' # 使用条件表达式过滤
stack
:追踪方法被调用的调用路径,找出该方法被哪些方法调用了
stack demo.MathGame primeFactors
stack demo.MathGame primeFactors 'params[0]<0' -n 2
stack demo.MathGame primeFactors '#cost>5'
tt
:时间隧道,time-tunnel,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测、重新调用等- 注意:tt命令时将当前环境的对象引用保存起来,但仅仅也只能保存一个引用而已。如果方法内部对入参进行了变更,或者返回的对象经过了后续的处理,那么在 tt 查看的时候将无法看到当时最准确的值。这也是为什么 watch 命令存在的意义。
tt -t demo.MathGame primeFactors # 记录调用现场,-n 3指定记录次数,-l列出已经记录的
# 解决重载方法的情况
tt -t *Test print params.length==1
tt -t *Test print 'params[1] instanceof Integer'
tt -t *Test print params[0].mobile=="13989838402"
tt -s 'method.name=="primeFactors"' # 筛选记录
tt -i 1003 # 查看调用详细信息,-d删除
tt -i 1032 -p --replay-times 2 --replay-interval 2 # -p重新执行方法,重新执行两次,间隔为2s
tt --delete-all # 删除所有记录 -d
tt -w 'target.illegalArgumentCount' -x 1 -i 1038 # 执行watch表达式,查看该方法内的一些值(有可能变化)
tt -w '@demo.MathGame@random.nextInt(100)' -x 1 -i 1038 # 执行静态方法
profiler/火焰图
:使用async-profiler对应用采样,生成火焰图。越高占用资源越多
profiler action [actionArg] # 命令运行基本结构
profiler actions # 查看所有的action
profiler list # 查看所有可以监控的event
profiler start # 开始采样,默认监控cpu,--duration 300指定时间,默认300s,--event alloc指定event
profiler start --include 'java/*' --include 'demo/*' --exclude '*Unsafe.park*' # 过滤数据
profiler getSamples # 查看已采样数量
profiler status # 查看状态,会显示已经采样多久
profiler stop # 停止采样, --format html指定格式,--file /tmp/output.svg指定输出路径
profiler resume # 恢复采样,会使用上次stop的数据
鉴权
java -jar arthas-boot.jar --password ppp # 启动时指定密码,--username指定用户名,默认用户名arthas
arthas.properties # 可以配置用户名和密码
arthas.localConnectionNonAuth=true # 默认本地连接不鉴权
options
:查看和修改arthas全局设置
后台异步任务
当线上出现偶发的问题,比如需要watch某个条件,而这个条件一天可能才会出现一次时,异步后台任务就派上用场了
Web Console
通过websocket连接Arthas,Arthas目前支持Web Console,用户在attach成功之后,可以直接访问本机3658端口,默认情况下,arthas只listen 127.0.0.1,所以如果想从远程连接,则可以使用 --target-ip参数指定listen的IP。会有安全风险,考虑tunnel server的方案
# 指定监听的ip,参数--attach-only只attach不连接
java -jar arthas/arthas-boot.jar --password ppp --target-ip x.x.x.x # 可以使用arthas ppp远程登录
Arthas Tunnel 远程管理多个agent
通过Arthas Tunnel Server/Client 来远程管理/连接多个Agent,agent启动后注册到tunnel server,通过tunnel server远程管理
- 先启动tunnel server:
java -jar arthas-tunnel-server-3.5.4-fatjar.jar --server.port=8000 --arthas.server.port=7000
,指定server端口和其他agent注册端口,默认8080,7777 - 启动agent:
java -jar arthas/arthas-boot.jar --tunnel-server ws://192.168.33.102:7000/ws --app-name demo --password ppp
,不要自己设置--agent-id,不符合规范前端不显示,设置--app-name即可- 或者通过修改配置文件arthas.properties,如果使用tunnel server访问,建议将telnetPort和httpPort设为0或-1,随机端口会不监听端口,只通过name区分是哪个
- 访问tunnel server:http://192.168.33.102:8000/apps.html ,显示所有连接上的应用,可以点击进行连接
- 配置的优先级是:命令行参数 > System Env > System Properties > arthas.properties,如果想要 arthas.properties的优先级最高,则可以配置 arthas.config.overrideAll=true
http://192.168.33.102:8000/actuator/arthas
,通过Spring Boot的Endpoint,可以查看到具体的连接信息,用户名arthas,密码在日志里
如果后启动的tunnel server,arthas attach的进程会自动连接,5s尝试一次
Arthas Properties 配置文件
如果是防止一个机器上启动多个 arthas端口冲突。可以配置为0随机端口,端口在日志里查看。或者配置为 -1不监听端口,并且通过tunnel server来使用arthas。--telnet-port
配置0或-1必须在配置文件里面配,--http-port
都可以,命令行优先
点击查看代码
#arthas.config.overrideAll=true
arthas.telnetPort=3658
arthas.httpPort=8563
arthas.ip=127.0.0.1
# seconds
arthas.sessionTimeout=1800
#arthas.enhanceLoaders=java.lang.ClassLoader
# https://arthas.aliyun.com/doc/en/auth
# arthas.username=arthas
# arthas.password=arthas
# local connection non auth, like telnet 127.0.0.1 3658
arthas.localConnectionNonAuth=true
#arthas.appName=demoapp
#arthas.tunnelServer=ws://127.0.0.1:7777/ws
#arthas.agentId=mmmmmmyiddddd
#arthas.disabledCommands=stop,dump
#arthas.outputPath=arthas-output
与SpringBoot整合
引入pom,在程序启动的时候自动attach到本进程,配置telnet-port
或http-port
即可通过web访问。如果注册到tunnel server,配置app-name
和tunnel-server
即可。
<dependency>
<groupId>com.taobao.arthas</groupId>
<artifactId>arthas-spring-boot-starter</artifactId>
<version>3.5.4</version>
</dependency>
本文来自博客园,作者:Bingmous,转载请注明原文链接:https://www.cnblogs.com/bingmous/p/15620831.html