bingmous

欢迎交流,不吝赐教~

导航

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端就可以访问了)

快速入门

  • dashboard:展示当前进程的信息,默认每5s更新一次,退出qCtrl + C
  • thread:显示当前进程所有的线程,查看某个线程的详细信息,thread ID
  • jad:反编译指定的类或方法,如jad demo.MathGame
  • watch:监视,如监控某个方法的返回值:watch demo.MathGame method returnObj
  • 其他命令
    • 退出arthas:qquitexit,此时attach到目标进程上的arthas还在继续运行,端口会保持开放,下次可以直接连上。
    • 完全退出:stop
    • tab自动补全(任何时候都可以使用,以提示相关信息),上箭头显示上次执行的命令,类似于linux

基础命令

  • help:查看命令帮助信息,某个命令的帮助信息加-h--help,或按tab自动补全
  • catechogrep(只能用于管道),base64teepwdwc -l,与linux里的类似
  • cls:清屏,linux:clear,windows:cls,mobaxterm使用Ctrl + L
  • session:显示当前会话信息,使用tunnel server时查看agentId
  • reset:重置增强类,将被 Arthas 增强过的类全部还原,Arthas 服务端关闭时会重置所有增强过的类。
    • 还原指定类、还原指定类的指定方法、还原正则匹配的类或方法,详见--helptab
  • version:显示arthas版本号
  • history:打印命令历史
  • quit:退出当前 Arthas 客户端,其他 Arthas 客户端不受影响,stop:关闭arthas服务端,所有客户端全部退出
  • keymap:Arthas快捷键列表及自定义快捷键

jvm相关

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:查看和修改logger
  • getstatic:通过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.txtbase64 -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"	# 在方法执行前执行条件表达式过滤统计结果

image

  • 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-porthttp-port即可通过web访问。如果注册到tunnel server,配置app-nametunnel-server即可。

<dependency>
	<groupId>com.taobao.arthas</groupId>
	<artifactId>arthas-spring-boot-starter</artifactId>
	<version>3.5.4</version>
</dependency>

posted on 2021-12-03 23:40  Bingmous  阅读(688)  评论(0编辑  收藏  举报