一、arthas能干什么?
- 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
- 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
- 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
- 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
- 是否有一个全局视角来查看系统的运行状况?
- 有什么办法可以监控到JVM的实时运行状态?
- 怎么快速定位应用的热点,生成火焰图?
- 怎样直接从JVM内查找某个类的实例?
Arthas支持JDK 6+
,支持Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 Tab 自动补全功能,进一步方便进行问题的定位和诊断。
二、安装与卸载
2.1 下载全量包
这里介绍离线安装,因为本公司的服务器是内网的。
下载路劲:https://arthas.aliyun.com/doc/download.html
2.2 上传到服务器并解压
## 利用xshell登录服务器,找个合适的位置,如:/usr/arthas
mkdir -p /usr/arthas
## 直接拖动下载zip包到这个位置完成上传
## 解压
unzui arthas-packaging-3.5.4-doc.zip
2.3 卸载
rm -rf ~/.arthas/
rm -rf ~/logs/arthas
三 命令
3.1 基础命令
- help—— 帮助文档
- cat —— 显示文件内容
- grep—— 管道符
- pwd—— 获取当前路径
- cls —— 清屏
- session —— 查看当前会话的信息
- reset——重置增强类,将被 Arthas 增强过的类全部还原,Arthas 服务端关闭时会重置所有增强过的类
- version——输出当前目标 Java 进程所加载的 Arthas 版本号
- quit——
★
退出当前 Arthas 客户端,其他 Arthas 客户端不受影响 - stop——
★
关闭 Arthas 服务端,所有 Arthas 客户端全部退出 - keymap ——Arthas快捷键列表及自定义快捷键
3.2 JVM相关命令
1 . ★dashboard——当前系统的实时数据面板
2 . ★thread——显示线程信息
3 .jvm——与JVM相关的信息
4. sysprop——查看或修改系统的属性信息
5. sysenv——查看当前JVM的环境属性
6. vmoption——查看,更新VM诊断相关的参数
7. ★getstatic——获取静态变量 推荐直接使用ognl命令,更加灵活
8. ★ognl——执行ognl表达式
3.2.1 dashboard -i 5000 -n 5
实用案例
# 每5s刷新一次共执行5次
dashboard -i 5000 -n 5
这儿有个笔误,里面所有的进程都是指线程,进程比线程要大,一个进程可能包含多个线程。
3.2.2 thread
实用案例
## 指定最忙的5个线程
thread -n 5
## (观察id线程)
thread id
## 查看死锁的线程
thread -b
3.2.3 jvm
使用案例
jvm
类加载相关
编译相关
垃圾回收,查看gloabal的垃圾回收是否太频繁
查看有没有死锁的线程
3.3 class/classloader相关的命令
sc :search class
sm:search method
1. sc——“Search-Class” 的简写,查看JVM已加载的类信息
2. sm——“Search-Method” 的简写,查看已加载类的方法信息
实例:查询一个类的类加载器的hashcode,这个hashcode在热更新时有用
实例:查询一个类的成员变量
## 查看这个类的详细信息,可以看到它是在哪个包或者它的包路径,之后可以用jad来反编译其中的某个方法。
sc -d *CaseEventManageServiceImpl
## -d 表示detail,查询一个类的类加载器的hashcode,这个hashcode在热更新时有用
sc -d *CaseEventManageServiceImpl | grep classLoaderHash
## 查询成员变量的值-f filed
sc -d -f *CaseEventManageServiceImpl
classloader 类加载器的应用场景
1.统计所有类加载器加载的对象的个数
2.查看类加载器的继承树
2.查找某个资源所在的包
## 统计所有类加载器加载的对象的个数
classloader
## 统计所有类加载器加载的对象的个数 带有hashcode
classloader -l
## 加载器树
classloader -t
##查找某个资源所在的包
classloader -c xxxx -r
查找类文件件所在的包
查找资源文件所在的包
★ 在线编译和反编译()
jad————反编译,把.class 文件编译成.java 文件
mc————Memory Compiler/内存编译器,编译.java文件生成.class。
sc————查找到类加载器的hashcode
redefine————加载外部的class文件
★四、最常用的3条命令
arthas如果半个小时不调用,就会自动退出
如何修改环境变量
watch 的 ognl
显示jdf中的方法 --skipJDKMethod false
只显示大于某个时间的方法 ‘#cost>0.5’
4.1 watch:用于监视方法执行情况
watch 命令定义了4个观察事件点,即 -b 函数调用前,-e 函数异常后,-s 函数返回后,-f 函数结束后,默认是-f
★使用性最全的语句
## 注意:instanceof 需要写类的全路径;#cost 表示接口请求时长
watch com.tiandy.testdemo.TestDemoApplication add '{params,returnObj,throwExp,target}' 'params.length>0 &¶ms[0].equals("aaa") && params[0] instanceof java.lang.Integer && params[0].getClass().getName().equals("java.lang.String") && #cost>1000 || params[0].contains("xxx") && params[1]=="abc" && params[2]==100L && params[3].name=="zhangsan" && params[4]["age"]==20 && params[5].size()==10' -n 5 -x 3
## 第 0个参数的名称是wolrd
params[0].name=="wolrd"
## map中获取age的用法
params[4]["age"]
## list的size
params[5].size()
## 通配符的使用
watch *TestDemoApplica* ?dd '{params,returnObj,throwExp}' -n 5 -x 3
通过条件筛选监听方法
通过通配符筛选,监听方法
4.2 trace: 方法执行过程
## 监听add方法,--skipJDKMethod false 不跳jdk的方法,'#cost > 10' 结果大于10毫秒
trace com.tiandy.testdemo.TestDemoApplication add 'params[0] instanceof java.lang.Double && #cost > 10' --skipJDKMethod false
4.3 stack: 输出当前方法的执行路径
## params.length==0 && #cost>5 无参+运行时长大于5ms
stack com.tiandy.testdemo.TestDemoApplication stringAdd 'params.length==0 && #cost>5' -n 5
★五 tt 时间隧道
TimeTunnel
记录方法的调用记录
通过条件过滤
查看某一次的调用结果
通过索引重新调用之前的方法
-t, --time-tunnel 按照时间记录
-p, --play
-i, --index
-l,–list 列出所有的记录
## 监听所有的类中的所有方法,第一个*表示类,第二个*表示方法
tt -t * *
## -n 5 此方法比较耗费jvm资源,一定要加上次数
tt -t com.tiandy.testdemo.TestDemoApplication add -n 5
## 超找方法名为add的片段,s 表示search 搜索
tt -s 'method.name=="add"'
## 查看所有监听的片段信息
tt -l
## 查看index为1003的具体信息
tt -i 1003
## 把1003的请求再执行一次
tt -i 1003 -p
## 查看 index 为1000的请求的method.name,params,returnObj,throwExp 这些信息
tt -w '{method.name,params,returnObj,throwExp}' -x 3 -i 1000
## 删除所有记录
tt --delete-all
六、热更新
六、火焰图
生成火焰图
七、Arthas最佳实战
7.1 已经有人在使用arthas,端口被占用
- 方法一:增加指定端口号
--telnet-port 9998 --http-port 8855
实例:
/opt/apache-tomcat/jre/bin/java -jar arthas-boot.jar `ps -ef | grep java_tomcat | grep -v grep | awk '{print $2}'` --telnet-port 9998 --http-port 8855
- 方法二:关闭之前的arthas,再打开
执行/opt/apache-tomcat/jre/bin/java -jar arthas-client.jar
,进入正在使用或者未关闭的arthas服务,执行stop,关闭所有的arthas客户端服务,执行正常的启动。
7.2 排查cpu过高
# 第一步:xshell中执行 找到占用cpu最高的【进程】
top -H -p pid
若果存在printf "%x\n" tid tid一直占用cpu的【线程id】
printf "%x\n" tid
# 第二步:使用thread看看线程cpu占用情况或dashbord也可以看到
thread -n 3
thread pid 显示指定线程的运行堆栈
# 第三步:拿到这个堆栈后反编译看代码,XXX代表类路径,xxx代表方法
jad --source-only XXX xxx
#★指定最忙的前N个线程并打印堆栈
thread -n pid
thread -b 找出当前阻塞其他线程的线程
thread -i 指定采样时间间隔 (如:thread -n 3 -i 1000,每秒统计最忙的前三个线层)
7.3 查看静态变量
实例
getstatic com.aaa.bbb formInfoMap
7.4 方法过多时观察方法,解决方法重载
只有一个参数
tt -t *Test print params.length1
指定参数类型
tt -t *Test print ‘params[1] instanceof Integer’
指定或包含参数值
tt -t *Test print params[0].mobile"13989838402"
tt -t *Test print params[0].mobile.contains(“13989838402”)
7.5 ★几种常用的条件表达式
tt -t *Test print params.length==1
tt -t *Test print 'params[1] instanceof Integer'
tt -t *Test print params[0].mobile=="13989838402"
tt -t *Test print params[0].contains("13989838402")
tt -t *Test print params[0].equals("13989838402")
## 执行时长大于20毫秒
watch *Test print '{params,returnObj,throwExp}' ‘#cost>200’ -n 1 -x 3
watch *Test print '{params[0],params[1],returnObj,throwExp}' ‘#cost>200’ -n 1 -x 3
7.6 ★ognl条件过滤
OGNL支持各种纷繁复杂的表达式。但是最最基本的表达式的原型,是将对象的引用值用点串联起来,从左到右,每一次表达式计算返回的结果成为当前对象,后面部分接着在当前对象上进行计算,一直到全部表达式计算完成,返回最后得到的对象。OGNL则针对这条基本原则进行不断的扩充,从而使之支持对象树、数组、容器的访问,甚至是类似SQL中的投影选择等操作。
- 基本对象树的访问
对象树的访问就是通过使用点号将对象的引用串联起来进行。
例如:xxxx,xxxx.xxxx,xxxx. xxxx. xxxx. xxxx. xxxx - 对容器变量的访问
对容器变量的访问,通过#符号加上表达式进行。
例如:#xxxx,#xxxx. xxxx,#xxxx.xxxxx. xxxx. xxxx. xxxx - 使用操作符号
OGNL表达式中能使用的操作符基本跟Java里的操作符一样,除了能使用 +, -, *, /, ++, --, ==, !=, = 等操作符之外,还能使用 mod, in, not in等。 - 容器、数组、对象
OGNL支持对数组和ArrayList等容器的顺序访问:例如:group.users[0]
同时,OGNL支持对Map的按键值查找:
例如:#session[‘mySessionPropKey’]
不仅如此,OGNL还支持容器的构造的表达式:
例如:{“green”, “red”, “blue”}构造一个List,#{“key1” : “value1”, “key2” : “value2”, “key3” : “value3”}构造一个Map
你也可以通过任意类对象的构造函数进行对象新建:
例如:new Java.net.URL(“xxxxxx/”) - 对静态方法或变量的访问
要引用类的静态方法和字段,他们的表达方式是一样的@class@member或者@class@method(args):
例如:@com.javaeye.core.Resource@ENABLE,@com.javaeye.core.Resource@getAllResources - 方法调用
直接通过类似Java的方法调用方式进行,你甚至可以传递参数:
例如:user.getName(),group.users.size(),group.containsUser(#requestUser) - 投影和选择
OGNL支持类似数据库中的投影(projection) 和选择(selection)。
投影就是选出集合中每个元素的相同属性组成新的集合,类似于关系数据库的字段操作。投影操作语法为 collection.{XXX},其中XXX 是这个集合中每个元素的公共属性。
例如:group.userList.{username}将获得某个group中的所有user的name的列表。
选择就是过滤满足selection 条件的集合元素,类似于关系数据库的纪录操作。选择操作的语法为:collection.{X YYY},其中X 是一个选择操作符,后面则是选择用的逻辑表达式。而选择操作符有三种:
? 选择满足条件的所有元素
^ 选择满足条件的第一个元素
$ 选择满足条件的最后一个元素
例如:group.userList.{? #txxx.xxx != null}将获得某个group中user的name不为空的user的列表。
7.6.1 投影和选择 类似与java中stream方法
watch com.xxx.xxx.xxx.business.controller.CaseFileController queryCaseFilePage '{params[0].sOrgId,returnObj.content.{? 1==1}.{? #this.sCurrentLocation=="\u6848\u7ba1\u5ba4\u002d\u0031\u53f7\u67dc\u5b50\u002d\u7bb1\u5b50\u0036"}.size().(#this>0?#this*10:0),throwExp}' '#cost>200&¶ms[0].clientIp=="10.30.50.39"&&returnObj.content.size()>0' -n 5 -x 3
ognl是从左向右执行,把左边的结果作为右边的入参继续执行,直到最终。
注意:如果ognl表达式中有中文,需要转为unicode才能正常筛选。
(#this>0?#this*10:0) 子表达式