Java 在线调试利器 Arthas
为什么要用它,能做什么
开发测试中经常遇到需要调试的时候,比如代码走到某个调用方法时这里的参数是什么值,方法出参是什么值?代码是否走了某个方法?优化接口耗时,是哪些个地方耗时比较长?
本地调试没问题,测试部署的版本有问题,代码是不是不一样?等等..
开发过程中,可以使用打日志、IDE的debug等方式,但如果已经把服务部署到测试或者生产环境中怎么办? IDE的远程debug 会阻塞服务,而且生产上都不会留这个口子。
arthas 可以解决这些痛点。
怎么做
安装
arthas 安装文档, arthas 提供在线安装 或者 下载压缩包直接解压使用的方式
- 在线安装
$ curl -O https://arthas.aliyun.com/arthas-boot.jar
$ java -jar arthas-boot.jar
- 下载压缩包、解压
https://arthas.aliyun.com/download/latest_version?mirror=aliyun
使用
$ java -jar arthas-boot.jar
,---. ,------. ,--------.,--. ,--. ,---. ,---.
/ O \ | .--. ''--. .--'| '--' | / O \ ' .-'
| .-. || '--'.' | | | .--. || .-. |`. `-.
| | | || |\ \ | | | | | || | | |.-' |
`--' `--'`--' '--' `--' `--' `--'`--' `--'`-----'
会列出来所有java进程,然后选择想要attach哪个进程,只需要输入序号即可。
常用命令
记不住没关系,直接输入 help 会告诉你有哪些命令列表。
JVM 相关
支持 JVM 相关查看的命令,这些命令大部分 JDK 原生或者操作系统提供,不做过多介绍。
dashboard
查看系统面板,比如线程占用CPU信息、内存使用情况、系统负载等。
thread
查看当前 JVM 的线程堆栈信息
# 查看当前占用 CPU,前 3 的线程栈情况
$ thread -n 3
trace/watch/stack 相关
请注意,这些命令,都通过字节码增强技术来实现的,会在指定类的方法中插入一些切面来实现数据统计和观测,因此在线上使用时,请尽量明确需要观测的类、方法以及条件,诊断结束要执行 stop 或将增强过的类执行 reset 命令。
trace 跟踪 某个方法 查看耗时等
trace [--exclude-class-pattern <value>] [-E] class-patt
ern method-pattern [condition-express]
# trace 全类名 方法名
$ trace com.soda.service.mybatis.MyBatisService mytest
# trace 全类名 所有方法
$ trace com.soda.service.mybatis.MyBatisService *
# trace 指定类、方法
trace -E com.test.ClassA|org.test.ClassB method1|method2|method3
# 耗时条件
trace *StringUtils isBlank '#cost>100'
watch 查看方法的入参、出参
这可以让我们不用加日志做debug
watch [-b] [-e] [--exclude-class-pattern <value>] [-E] clas
s-pattern method-pattern [express] [condition-express]
# watch 全类名 方法名
$ watch com.soda.service.mybatis.MyBatisService mytest '{params, target, returnObj, throwExp}' -x 2
如果懒得写具体方法名,也可以直接写 *,全部方法名都监控。如果想看具体哪个参数或者关键字,也支持 grep 或者 ognl表达式
$ watch com.soda.service.mybatis.MyBatisService * '{params, target, returnObj, throwExp}' -x 2 | grep User
其中 ’{params, target, returnObj, throwExp}’ 是约定的参数
- params 是方法入参,比如只看第1个参数 params[0]
- target 是调用该方法的具体类实例
- returnObj 是该方法的返回值参数
- throwExp 是该方法抛的异常
-x 2 表示查看对象的第几层级,默认是1,一般至少要看到层级2。
ognl表达式过滤
# 查看 '{params, target, returnObj, throwExp}', 条件是 第一个参数为字符串aa
watch com.soda.service.AnotherTestService myTest '{params, target, returnObj, throwExp}' 'params[0]=="aa"' -x 2
stack 看下这个方法谁调用过来的
可能某个方法有多个地方被调用,可以用stack调试
$ stack com.soda.service.mybatis.MyBatisService myTest
热加载 class/classloader 相关
热加载的功能对快速修复问题很有帮助。
jad 反编译
可能我们对某个类有疑问,想确认部署的版本是不是我加了某个逻辑,可以利用 jad 反编译某个类查看源代码。
jad 全类名 方法名(方法名可有可无)
# 记不住没关系 help
$ help jad
# 只看源码 写到/tmp目录下
$ jad --source-only com.soda.service.mybatis.MyBatisService > /tmp/MyBatisService.java
sc 查看 JVM 已加载的类信息
查看类信息, -d 查看detail详情,包括类加载器 classLoader信息。
$ sc -d *MyBatisService
$ sc -d *MyBatisService | grep classLoaderHash
sm 查看已加载类的方法信息
查看类下的所有方法详细信息
$ sm -d *MyBatisService
# 类名 方法名
$ sm -d com.soda.service.mybatis.MyBatisService mytest
mc 编译源码
编译源码,并指定某个类加载器。
-c 指定类加载器的 hashcode,如上 sc xx | grep classLoaderHash
-d 输出到某个目录下
$ mc -c 18b4aac2 /tmp/MyBatisService.java -d /tmp
retransform 热加载
加载外部 .class 文件到JVM里。
$ retransform /tmp/com/soda/service/mybatis/MyBatisService.class
tt 用法
Time Tunnel,可以记录并能回放请求。适合不方便多次请求,可以把一次请求记录下来,再回放处理。
每次抓取会分配一个index
EXAMPLES:
## 抓取该类方法下的请求
tt -t *StringUtils isEmpty
## 带条件抓取
tt -t *StringUtils isEmpty params[0].length==1
## 列出来
tt -l
## 显示具体哪个片段(index)
tt -i 1000
tt -i 1000 -w params[0]
## 重放1000号请求
tt -i 1000 -p
tt -i 1000 -p --replay-times 3 --replay-interval 3000
tt -s '{params[0] > 1}' -w '{params}'
## 删除
tt --delete-all
热加载流程
- 反编译
- 修改源代码
- 查找该类的类加载器
- 编译
- 加载.class 到 JVM
1. jad com.soda.service.mybatis.MyBatisService --source-only > /tmp/MyBatisService.java
2. vi /tmp/MyBatisService.java
3. sc -d *MyBatisService | grep classLoaderHash
4. mc -c 18b4aac2 /tmp/MyBatisService.java -d /tmp
5. retransform /tmp/com/soda/service/mybatis/MyBatisService.class
进阶用法 Ognl
arthas可以和OGNL结合使用,使得功能更丰富。
尤其spring开发时,我们想看看spring容器里的某个bean,或者执行下这个bean的某个方法。
ognl [-c <value>] [-x <value>] express
ognl '@java.lang.System@out.println("hello \u4e2d\u6587")'
ognl -x 2 '@Singleton@getInstance()'
ognl '@Demo@staticFiled'
ognl '#value1=@System@getProperty("java.home"), #value2=@System@getProperty("java.runtime.name"), {#value1, #value2}'
ognl -c 5d113a51 '@com.taobao.arthas.core.GlobalOptions@isDump'
静态类、方法前使用符号@,普通方法用符号.
举例
通常我们项目里会有通过实现 ApplicationContextAware 获取spring容器的方式:
@Component
public class SpringContext implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
public <T> T getBean(Class<T> clazz) {
return context.getBean(clazz);
}
}
# OGNL获取该spring容器
ognl "@com.xxx.SpringContext@context"
# 获取某个bean,并执行bean下的某个方法
ognl "@com.xxx.SpringContext@context.getBean('beanName').methodName()"
# 多个语句的命令
ognl "#p=new com.xxx.Student(),#p.setName('name1'),#p.setAge(10), @com.xx.SpringContext@context.getBean('beanName').methodName(#p, 'xx')"
# 指定类加载器的用法,先找到类加载器
1. sc -d com.xxx.SpringContext | grep classLoaderHash
2. ognl -c hashValue "cmd..."