赞
踩
Arthas是alibaba开源的java诊断工具,支持jdk6+,采用命令行交互模式,可以防败的定位和诊断线上的程序运行问题。官方文档:https://arthas.aliyun.com/doc/
是否有一个全局视角来查看系统的运行状况?
为什么 CPU 又升高了,到底是哪里占用了 CPU ?
运行的多线程有死锁吗?有阻塞吗?
程序运行耗时很长,是哪里耗时比较长呢?如何监测呢?
这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
有什么办法可以监控到 JVM 的实时运行状态?
- # github下载arthas
- wget https://alibaba.github.io/arthas/arthas-boot.jar
- # 或者 Gitee 下载
- wget https://arthas.gitee.io/arthas-boot.jar
在你要监控的应用启动成功之后,再启动Arthas程序
java -jar arthas-boot
选中需要监控的java程序pid
- package cn.phlos.csdn.demo;
-
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.PathVariable;
- import org.springframework.web.bind.annotation.RestController;
-
- import java.util.Arrays;
- import java.util.HashSet;
- import java.util.List;
- import java.util.Random;
-
- /**
- * @ClassName: DemoController
- * @Author: lph
- * @Description:
- * @Date: 2023/1/5 22:07
- */
- @RestController
- public class DemoController {
-
- private static HashSet hashSet = new HashSet();
-
- /**
- * 模拟线程死锁,cup过高
- */
- @GetMapping("/thread")
- public void thread() {
- // 模拟 CPU 过高
- cpuHigh();
- // 模拟线程死锁
- deadThread();
- // 不断的向 hashSet 集合增加数据
- addHashSetThread();
- }
-
- /**
- * 模拟耗时
- */
- @GetMapping("/cost")
- public void cost(){
- for (int i = 0; i < 10; i++) {
- threadCost();
- hashSet.add(""+i);
- }
- }
-
-
- @GetMapping("/watch/{num}")
- public Integer watch(@PathVariable("num") Integer num){
- Random random = new Random();
- List<Integer> list = Arrays.asList(random.nextInt(100), random.nextInt(50));
- hashSet.add(""+1);
- return list.get(0)+list.get(1);
- }
-
- private void threadCost(){
- int nextInt = new Random().nextInt(20)+1;
- try {
- Thread.sleep(nextInt*10);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
-
- }
-
-
-
- /**
- * 不断的向 hashSet 集合添加数据
- */
- public void addHashSetThread() {
- // 初始化常量
- new Thread(() -> {
- int count = 0;
- while (true) {
- try {
- hashSet.add("count" + count);
- Thread.sleep(1000);
- count++;
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }).start();
- }
-
- public void cpuHigh() {
- new Thread(() -> {
- while (true) {
-
- }
- }).start();
- }
-
- /**
- * 死锁
- */
- private void deadThread() {
- /** 创建资源 */
- Object resourceA = new Object();
- Object resourceB = new Object();
- // 创建线程
- Thread threadA = new Thread(() -> {
- synchronized (resourceA) {
- System.out.println(Thread.currentThread() + " get ResourceA");
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread() + "waiting get resourceB");
- synchronized (resourceB) {
- System.out.println(Thread.currentThread() + " get resourceB");
- }
- }
- });
-
- Thread threadB = new Thread(() -> {
- synchronized (resourceB) {
- System.out.println(Thread.currentThread() + " get ResourceB");
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread() + "waiting get resourceA");
- synchronized (resourceA) {
- System.out.println(Thread.currentThread() + " get resourceA");
- }
- }
- });
- threadA.start();
- threadB.start();
-
- }
-
-
- }
或是下载该项目的jar包在本地上运行,按照步骤操作:
监控指定类中方法的执行情况、用来见识一个时间短指定方法的执行次数,成功次数、失败次数,耗时等这些信息
参数说明:
方法拥有一个明明参数[c:],意思是统计周期,为一个整数的类型
参数名称 | 参数说明 |
class-pattern | 类名表达式匹配 |
method-pattern | 方法名表达式匹配 |
condition-express | 条件表达式 |
[E] | 开启正则表达式匹配,默认为通配符匹配 |
[c:] | 统计周期,默认值为 120 秒 |
[b] | 在方法调用之前计算 condition-express |
案例1:
- #监控接口的实现方法,并且3S更新一次状态
- monitor cn.phlos.csdn.demo.DemoController cost -c 3
调用接口:localhost:8080/cost
就可以看到监控到这个方法的运行,每3秒打印一次
监控的维度说明
监控项 | 说明 |
timestamp | 时间戳 |
class | Java 类 |
method | 方法(构造方法、普通方法) |
total | 调用次数 |
success | 成功次数 |
fail | 失败次数 |
rt | 平均 RT |
fail-rate | 失败率 |
方法执行数据观测,让你能方便的观察到指定方法的调用情况。
能观察到的范围为: 返回值、 抛出异常、 入参,通过编写OGNL 表达式进行对应变量的查看。
参数说明:
watch 的参数比较多,主要是因为它能在 4 个不同的场景观察对象
参数名称 | 参数说明 |
class-pattern | 类名表达式匹配 |
method-pattern | 函数名表达式匹配 |
express | 观察表达式,默认值:{params, target, returnObj} |
condition-express | 条件表达式 |
[b] | 在函数调用之前观察 |
[e] | 在函数异常之后观察 |
[s] | 在函数返回之后观察 |
[f] | 在函数结束之后(正常返回和异常返回)观察 |
[E] | 开启正则表达式匹配,默认为通配符匹配 |
[x:] | 指定输出结果的属性遍历深度,默认为 1,最大值是 4 |
这里重点要说明的是观察表达式,观察表达式的构成主要由 ognl 表达式组成,所以你可以这样写"{params,returnObj}",只要是一个合法的 ognl 表达式,都能被正常支持
特别说明:
watch 命令定义了 4 个观察事件点,即 -b 函数调用前,-e 函数异常后,-s 函数返回后,-f 函数结束后
4 个观察事件点 -b、-e、-s 默认关闭,-f 默认打开,当指定观察点被打开后,在相应事件点会对观察表达式进行求值并输出
这里要注意函数入参和函数出参的区别,有可能在中间被修改导致前后不一致,除了 -b 事件点 params 代表函数入参外,其余事件都代表函数出参
当使用 -b 时,由于观察事件点是在函数调用前,此时返回值或异常均不存在
在 watch 命令的结果里,会打印出location信息。location有三种可能值:AtEnter,AtExit,AtExceptionExit。对应函数入口,函数正常 return,函数抛出异常。
案例1:
- # 查看方法执行的返回值
- watch cn.phlos.csdn.demo.DemoController watch returnObj
- # 观察DemoController类中watch方法出参和返回值,结果属性遍历深度为2
- # params:表示所有参数数组(因为不确定是几个参数)。
- # returnObject:表示返回值
- watch cn.phlos.csdn.demo.DemoController watch "{params,returnObj}" -x 2
执行完命令,调用接口:localhost:8080/watch/2,即可看到数据
案例2:
- #-b 查看方法执行前的参数
- watch cn.phlos.csdn.demo.DemoController watch "{params,returnObj}" -x 2 -b
案例3:
- #查看方法中的属性
- watch cn.phlos.csdn.demo.DemoController watch "{target}" -x 2 -b
案例4:
- #检测方法在执行前-b、执行后-s的入参params、属性target和返回值returnObj
- watch cn.phlos.csdn.demo.DemoController watch "{params,target,returnObj}" -x 2 -b -s -n 2
案例5:
- #输入参数小于10的情况
- watch cn.phlos.csdn.demo.DemoController watch "{params[0],target}" "params[0]<10"
执行:localh0ost:8080/watch/2、localhost:8080/watch/9、localhost:8080/watch/20
案例6:
- #按照耗时进行过滤
- watch cn.phlos.csdn.demo.DemoController watch "{params,returnObj}" "#cost>0.01" -x 2
方法内部调用路径,并输出方法路径上的每个节点上耗时
trace 命令能主动搜索 class-pattern/method-pattern 对应的方法调用路径,渲染和统计整个调用链路上的所有性能开销和追踪调用链路。
参数说明
参数名称 | 参数说明 |
class-pattern | 类名表达式匹配 |
method-pattern | 方法名表达式匹配 |
condition-express | 条件表达式 |
[E] | 开启正则表达式匹配,默认为通配符匹配 |
[n:] | 命令执行次数 |
#cost | 方法执行耗时 |
这里重点要说明的是观察表达式,观察表达式的构成主要由 ognl 表达式组成,所以你可以这样写"{params,returnObj}",只要是一个合法的 ognl 表达式,都能被正常支持。
观察的维度也比较多,主要体现在参数 advice 的数据结构上。Advice 参数最主要是封装了通知节点的所有信息。
案例1:
- # trace函数指定类的指定方法
- trace cn.phlos.csdn.demo.DemoController cost
调用接口:localhost:8080/cost
案例2:
- # 执行一次后退出
- trace cn.phlos.csdn.demo.DemoController cost -n 1
案例3:
- #默认情况下,trace不会包含jdk里的函数调用,如果希望trace jdk里的函数。
- #需要显式设置--skipJDKMethod false。
- trace --skipJDKMethod false cn.phlos.csdn.demo.DemoController cost
案例4:
- #据调用耗时过滤,trace大于100ms的调用路径
- trace cn.phlos.csdn.demo.DemoController cost '#cost > 100'
只会展示耗时大于 10ms 的调用路径,有助于在排查问题的时候,只关注异常情
是不是很眼熟,没错,在 JProfiler 等收费软件中你曾经见识类似的功能,这里你将可以通过命令就能打印出指定调用路径。 友情提醒下,trace 在执行的过程中本身是会有一定的性能开销,在统计的报告中并未像 JProfiler 一样预先减去其自身的统计开销。所以这统计出来有些许的不准,渲染路径上调用的类、方法越多,性能偏差越大。但还是能让你看清一些事情的。
[1127.5045ms] 的含义,1127.5045 的含义是:当前节点在当前步骤的耗时,单位为毫秒
[0,0,0ms,11]xxx:yyy() [throws Exception],对该方法中相同的方法调用进行了合并,0,0,0ms,11 表示方法调用耗时,min,max,total,count;throws Exception 表明该方法调用中存在异常返回
这里存在一个统计不准确的问题,就是所有方法耗时加起来可能会小于该监测方法的总耗时,这个是由于 Arthas 本身的逻辑会有一定的耗时
案例5:
trace 命令只会 trace 匹配到的函数里的子调用,并不会向下 trace 多层。因为 trace 是代价比较贵的,多层 trace 可能会导致最终要 trace 的类和函数非常多。
可以用正则表匹配路径上的多个类和函数,一定程度上达到多层 trace 的效果
- # 可以用正则表匹配路径上的多个类和函数,一定程度上达到多层trace的效果。
- trace -E com.test.ClassA|org.test.ClassB method1|method2|method3
案例6:
- # 使用 --exclude-class-pattern 参数可以排除掉指定的类
- trace javax.servlet.Filter * --exclude-class-pattern com.demo.TestFilter
很多时候我们都知道一个方法被执行,但这个方法被执行的路径非常多,或者你根本就不知道这个方法是从那里被执行了,此时你需要的是 stack 命令。
参数说明
参数名称 | 参数说明 |
class-pattern | 类名表达式匹配 |
method-pattern | 方法名表达式匹配 |
condition-express | 条件表达式 |
[E] | 开启正则表达式匹配,默认为通配符匹配 |
[n:] | 执行次数限制 |
这里重点要说明的是观察表达式,观察表达式的构成主要由 ognl 表达式组成,所以你可以这样写"{params,returnObj}",只要是一个合法的 ognl 表达式,都能被正常支持。
案例1:
- #获取cost的调用路径
- stack cn.phlos.csdn.demo.DemoController cost
案例2:
- # 条件表达式来过滤,第0个参数的值小于0,-n表示获取2次
- stack cn.phlos.csdn.demo.DemoController watch 'params[0]<0' -n 2
调用:localhost:8080/watch/-9、localhost:8080/watch/-9
案例3:
- # 据执行时间来过滤,耗时大于100毫秒
- stack cn.phlos.csdn.demo.DemoController cost '#cost>100'
方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测
watch 虽然很方便和灵活,但需要提前想清楚观察表达式的拼写,这对排查问题而言要求太高,因为很多时候我们并不清楚问题出自于何方,只能靠蛛丝马迹进行猜测。
这个时候如果能记录下当时方法调用的所有入参和返回值、抛出的异常会对整个问题的思考与判断非常有帮助。
于是乎,TimeTunnel 命令就诞生了。
参数说明
参数名称 | 参数说明 |
-t | 记录某一个方法在一个时间段中的调用 |
-l | 显示所有已经记录的列表 |
-n 次数 | 只记录多少次 |
-s 表达式 | 搜索表达式 |
-i 索引号 | 查看指定索引号的详细调用信息 |
-p | 重新调用:指定的所有号时间碎片 |
命令参数解析
-t
tt 命令有很多个主参数,-t 就是其中之一。这个参数的表明希望记录下类 *Test 的 print 方法的每次执行情况。
-n 3
当你执行一个调用量不高的方法时可能你还能有足够的时间用 CTRL+C 中断 tt 命令记录的过程,但如果遇到调用量非常大的方法,瞬间就能将你的 JVM 内存撑爆。
此时你可以通过 -n 参数指定你需要记录的次数,当达到记录次数时 Arthas 会主动中断 tt 命令的记录过程,避免人工操作无法停止的情况。
案例1:
- # 最基本的使用来说,就是记录下当前方法的每次调用环境现场。
- tt -t cn.phlos.csdn.demo.DemoController cost
表格字段说明
表格字段 | 字段解释 |
INDEX | 时间片段记录编号,每一个编号代表着一次调用,后续 tt 还有很多命令都是基于此编号指定记录操作,非常重要。 |
TIMESTAMP | 方法执行的本机时间,记录了这个时间片段所发生的本机时间 |
COST(ms) | 方法执行的耗时 |
IS-RET | 方法是否以正常返回的形式结束 |
IS-EXP | 方法是否以抛异常的形式结束 |
OBJECT | 执行对象的hashCode(),注意,曾经有人误认为是对象在 JVM 中的内存地址,但很遗憾他不是。但他能帮助你简单的标记当前执行方法的类实体 |
CLASS | 执行的类名 |
METHOD | 执行的方法名 |
案例2:
- #对现有记录进行检索
- tt -l
案例3:
- # 需要筛选出 `cost` 方法的调用信息
- tt -s 'method.name=="cost"'
案例4:
- # 查看某条记录详细信息
- tt -i 1000
案例5:
- #重做一次调用
- tt -i 1000 -p
当你稍稍做了一些调整之后,你可能需要前端系统重新触发一次你的调用,此时得求爷爷告奶奶的需要前端配合联调的同学再次发起一次调用。而有些场景下,这个调用不是这么好触发的。
tt 命令由于保存了当时调用的所有现场信息,所以我们可以自己主动对一个 INDEX 编号的时间片自主发起一次调用,从而解放你的沟通成本。此时你需要 -p 参数。通过 --replay-times 指定 调用次数,通过 --replay-interval 指定多次调用间隔(单位 ms, 默认 1000ms)
你会发现结果虽然一样,但调用的路径发生了变化,由原来的程序发起变成了 Arthas 自己的内部线程发起的调用了。
序号 | 基础命令 | 功能 |
1 | help | 显示所有arthas命令,每个命令都可以使用-h的参数,显示它的参数信息 |
2 | cat | 显示文本文件内容 |
3 | grep | 对内容进行过滤,只显示关心的行 |
4 | pwd | 显示当前的工作路径 |
5 | session | 显示当前连接的会话ID |
6 | reset | 重置arthas增强的类 |
7 | version | 显示当前arthas的版本号 |
8 | history | 查看历史命令 |
9 | cls | 清除屏幕 |
10 | quit | 退出当前的会话 |
11 | stop | 结束arthas服务器,退出所有的会话 |
12 | keymap | 显示所有的快捷键 |
可以查看当前 arthas 版本支持的指令,或者查看具体指令的使用说明
[help 指令]的等同于[指令 -help],都是查看具体指令的使用说明。
参数说明
参数名称 | 参数说明 |
不接参数 | 查询当前 arthas 版本支持的指令以及指令描述 |
[name:] | 查询具体指令的使用说明 |
打印文件内容,和 linux 里的 cat 命令类似。
类似传统的grep命令。
USAGE: grep [-A <value>] [-B <value>] [-C <value>] [-h] [-i] [-v] [-n] [-m <value>] [-e] [--trim-end] pattern SUMMARY: grep command for pipes. EXAMPLES: sysprop | grep java sysprop | grep java -n sysenv | grep -v JAVA sysenv | grep -e "(?i)(JAVA|sun)" -m 3 -C 2 sysenv | grep JAVA -A2 -B3 thread | grep -m 10 -e "TIMED_WAITING|WAITING" WIKI: https://arthas.aliyun.com/doc/grep OPTIONS: -A, --after-context <value> Print NUM lines of trailing context) -B, --before-context <value> Print NUM lines of leading context) -C, --context <value> Print NUM lines of output context) -h, --help this help -i, --ignore-case Perform case insensitive matching. By default, grep is case sensitive. -v, --invert-match Select non-matching lines -n, --line-number Print line number with output lines -m, --max-count <value> stop after NUM selected lines) -e, --regex Enable regular expression to match --trim-end Remove whitespaces at the end of the line <pattern> Pattern
案例:
- sysprop |grep "java" # 只显示包含java字符串的行系统属性
- sysprop |grep "java" -n # 显示行号
- sysprop |grep "java" -n -m10 # 显示行号,只显示10行
- thread | grep -e "o+" # 使用正则表达式,显示包含2个o字符的线程信息
返回当前的工作目录,和 linux 命令类似
如果配置了 tunnel server,会追加打印 代理 id、tunnel 服务器的 url 以及连接状态。
如果使用了 staturl 做统计,会追加显示 statUrl 地址。
重置增强类,将被 Arthas 增强过的类全部还原,Arthas 服务端stop时会重置所有增强过的类
- reset Test # 还原指定类
- reset *List # 还原所有以List结尾的类
- reset # 还原所有的类
案例:
- #查询方法耗时
- trace cn.phlos.csdn.demo.DemoController cost
- #所有的类
- reset
输出当前目标 Java 进程所加载的 Arthas 版本号
历史指令会通过一个名叫 history 的文件持久化,所以 history 指令可以查看当前 arthas 服务器的所有历史命令,而不仅只是当前次会话使用过的命令
参数说明
参数名称 | 参数说明 |
[c:] | 清空历史指令 |
[n:] | 显示最近执行的 n 条指令 |
- #查看最近执行的3条指令
- history 3
-
- #清空指令
- history -c
非终端模式下使用 cls 指令,会提示"Command 'cls' is only support tty session."。
只是退出当前 Arthas 客户端,Arthas 的服务器端并没有关闭,所做的修改也不会被重置。
其他 Arthas 客户端不受影响。等同于 exit、 logout、 q三个指令。
关闭 Arthas 服务器之前,会重置掉所有做过的增强类。但是用 redefine 重加载的类内容不会被重置
默认的快捷键如下:
快捷键 | 快捷键说明 | 命令名称 | 命令说明 |
"\C-a" | ctrl + a | beginning-of-line | 跳到行首 |
"\C-e" | ctrl + e | end-of-line | 跳到行尾 |
"\C-f" | ctrl + f | forward-word | 向前移动一个单词 |
"\C-b" | ctrl + b | backward-word | 向后移动一个单词 |
"\e[D" | 键盘左方向键 | backward-char | 光标向前移动一个字符 |
"\e[C" | 键盘右方向键 | forward-char | 光标向后移动一个字符 |
"\e[B" | 键盘下方向键 | next-history | 下翻显示下一个命令 |
"\e[A" | 键盘上方向键 | previous-history | 上翻显示上一个命令 |
"\C-h" | ctrl + h | backward-delete-char | 向后删除一个字符 |
"\C-?" | ctrl + shift + / | backward-delete-char | 向后删除一个字符 |
"\C-u" | ctrl + u | undo | 撤销上一个命令,相当于清空当前行 |
"\C-d" | ctrl + d | delete-char | 删除当前光标所在字符 |
"\C-k" | ctrl + k | kill-line | 删除当前光标到行尾的所有字符 |
"\C-i" | ctrl + i | complete | 自动补全,相当于敲TAB |
"\C-j" | ctrl + j | accept-line | 结束当前行,相当于敲回车 |
"\C-m" | ctrl + m | accept-line | 结束当前行,相当于敲回车 |
"\C-w" | backward-delete-word | ||
"\C-x\e[3~" | backward-kill-line | ||
"\e\C-?" | backward-kill-word |
任何时候 tab 键,会根据当前的输入给出提示
命令后敲 - 或 -- ,然后按 tab 键,可以展示出此命令具体的选项
后台异步命令相关快捷键
ctrl + c: 终止当前命令
ctrl + z: 挂起当前命令,后续可以 bg/fg 重新支持此命令,或 kill 掉
ctrl + a: 回到行首
ctrl + e: 回到行尾
序号 | 命令 | 功能说明 |
1 | dashboard | 仪表板,可以显示:线程,内存,堆栈,GC,Runtime等信息 |
2 | thread | 显示线程信息 |
3 | jvm | 与JVM相关的信息 |
4 | sysprop | 显示系统属性信息,也可以修改某个属性 |
5 | sysenv | 查看JVM环境变量的值 |
6 | vmoption | 查看JVM中选项,可以修改 |
7 | getstatic | 获取静态成员变量 |
8 | ognl | 执行一条ognl表达式,对象图导航语言 |
当前系统的实时数据面板,按 ctrl+c 退出
当运行在 Ali-tomcat 时,会显示当前 tomcat 的实时信息,如 HTTP 请求的 qps, rt, 错误数, 线程池信息等等。
参数说明
参数名称 | 参数说明 |
[i:] | 刷新实时数据的时间间隔 (ms),默认 5000ms |
[n:] | 刷新实时数据的次数 |
字段说明:
ID: Java 级别的线程 ID,注意这个 ID 不能跟 jstack 中的 nativeID 一一对应。
NAME: 线程名
GROUP: 线程组名
PRIORITY: 线程优先级, 1~10 之间的数字,越大表示优先级越高
STATE: 线程的状态
CPU%: 线程的 cpu 使用率。比如采样间隔 1000ms,某个线程的增量 cpu 时间为 100ms,则 cpu 使用率=100/1000=10%
DELTA_TIME: 上次采样之后线程运行增量 CPU 时间,数据格式为秒
TIME: 线程运行总 CPU 时间,数据格式为分:秒
INTERRUPTED: 线程当前的中断位状态
DAEMON: 是否是 daemon 线程
JVM 内部线程
Java 8 之后支持获取 JVM 内部线程 CPU 时间,这些线程只有名称和 CPU 时间,没有 ID 及状态等信息(显示 ID 为-1)。 通过内部线程可以观测到 JVM 活动,如 GC、JIT 编译等占用 CPU 情况,方便了解 JVM 整体运行状况。
当 JVM 堆(heap)/元数据(metaspace)空间不足或 OOM 时,可以看到 GC 线程的 CPU 占用率明显高于其他的线程。
当执行trace/watch/tt/redefine等命令后,可以看到 JIT 线程活动变得更频繁。因为 JVM 热更新 class 字节码时清除了此 class 相关的 JIT 编译结果,需要重新编译。
JVM 内部线程包括下面几种:
JIT 编译线程: 如 C1 CompilerThread0, C2 CompilerThread0
GC 线程: 如GC Thread0, G1 Young RemSet Sampling
其它内部线程: 如VM Periodic Task Thread, VM Thread, Service Thread
查看当前线程信息,查看线程的堆栈
参数说明
参数名称 | 参数说明 |
id | 线程 id |
[n:] | 指定最忙的前 N 个线程并打印堆栈 |
[b] | 找出当前阻塞其他线程的线程 |
[i <value>] | 指定 cpu 使用率统计的采样间隔,单位为毫秒,默认值为 200 |
[--all] | 显示所有匹配的线程 |
- thread # 显示所有线程的信息
- thread 1 # 显示1号线程的运行堆栈
- thread -b # 查看阻塞的线程信息
- thread -n 3 # 查看最忙的3个线程,并打印堆栈
- thread -i 1000 -n 3 # 指定采样时间间隔,每过1000毫秒采样,显示最占时间的3个线程
- thread --state WAITING # 查看处于等待状态的线程(WAITING、BLOCKED)
案例1:
- thread # 查看线程状态
- thread -b # 查看阻塞的线程信息
案例2:
- #查看处于等待状态的线程(WAITING、BLOCKED)
- thread --state WAITING
THREAD 相关
COUNT: JVM 当前活跃的线程数
DAEMON-COUNT: JVM 当前活跃的守护线程数
PEAK-COUNT: 从 JVM 启动开始曾经活着的最大线程数
STARTED-COUNT: 从 JVM 启动开始总共启动过的线程次数
DEADLOCK-COUNT: JVM 当前死锁的线程数
文件描述符相关
MAX-FILE-DESCRIPTOR-COUNT:JVM 进程最大可以打开的文件描述符数
OPEN-FILE-DESCRIPTOR-COUNT:JVM 当前打开的文件描述符数
查看当前 JVM 的系统属性(System Property)
案例:
- sysprop # 查看所有属性
- sysprop java.version # 查看单个属性,支持通过tab补全
- sysprop user.country #查看
- sysprop user.country US #修改
查看当前 JVM 的环境属性(System Environment Variables)
- sysenv # 查看所有环境变量
- sysenv USER # 查看单个环境变量
查看,更新 VM 诊断相关的参数
- vmoption # 查看所有的选项
- vmoption PrintGCDetails # 查看指定的选项
- vmoption PrintGCDetails true # 更新指定的选项
- # 语法
- getstatic 类名 属性名
-
- #显示DemoController的静态属性hashSet
- getstatic cn.phlos.csdn.demo.DemoController hashSet
参数说明
参数名称 | 参数说明 |
express | 执行的表达式 |
[c:] | 执行表达式的 ClassLoader 的 hashcode,默认值是 SystemClassLoader |
[classLoaderClass:] | 指定执行表达式的 ClassLoader 的 class name |
[x] | 结果对象的展开层次,默认值 1 |
案例:
调用静态函数
ognl '@java.lang.System@out.println("hello")'
获取静态类的静态字段:
ognl '@cn.phlos.csdn.demo.DemoController@hashSet'
执行多行表达式,赋值给临时变量,返回一个List
- # 计算value1、value2值,并存在List集合中
- ognl '#value1=@System@getProperty("java.home"), #value2=@System@getProperty("java.runtime.name"), {#value1, #value2}'
序号 | 命令 | 功能说明 |
1 | sc | Search Class 查看运行中的类信息 |
2 | sm | Search Method 查看类中方法的信息 |
3 | jad | 反编译字节码为源代码 |
4 | mc | Memory Compile 将源代码编译成字节码 |
5 | redefine | 将编译好的字节码文件加载到jvm中运行 |
6 | dump | 加载类的 bytecode 到特定目录 |
7 | classloader | 查看类加载信息 |
“Search-Class” 的简写,这个命令能搜索出所有已经加载到 JVM 中的 Class 信息,这个命令支持的参数有 [d]、 [E]、 [f] 和 [x:]。
参数说明
参数名称 | 参数说明 |
class-pattern | 类名表达式匹配 |
method-pattern | 方法名表达式匹配 |
[d] | 输出当前类的详细信息,包括这个类所加载的原始文件来源、类的声明、加载的 ClassLoader 等详细信息。 如果一个类被多个 ClassLoader 所加载,则会出现多次 |
[E] | 开启正则表达式匹配,默认为通配符匹配 |
[f] | 输出当前类的成员变量信息(需要配合参数-d 一起使用) |
[x:] | 指定输出静态变量时属性的遍历深度,默认为 0,即直接使用 toString 输出 |
[c:] | 指定 class 的 ClassLoader 的 hashcode |
[classLoaderClass:] | 指定执行表达式的 ClassLoader 的 class name |
[n:] | 具有详细信息的匹配类的最大数量(默认为 100) |
[cs <arg>] | 指定 class 的 ClassLoader#toString() 返回值。长格式[classLoaderStr <arg>] |
class-pattern 支持全限定名,如 com.taobao.test.AAA,也支持 com/taobao/test/AAA 这样的格式,这样,我们从异常堆栈里面把类名拷贝过来的时候,不需要在手动把/替换为.啦。
sc 默认开启了子类匹配功能,也就是说所有当前类的子类也会被搜索出来,想要精确的匹配,请打开options disable-sub-class true开关
- sc cn.phlos.* # 模糊搜索,demo包下所有的类
- sc -d cn.phlos.csdn.demo.DemoController # 打印类的详细信息
“Search-Method” 的简写,这个命令能搜索出所有已经加载了 Class 信息的方法信息。
sm 命令只能看到由当前类所声明 (declaring) 的方法,父类则无法看到。
参数说明
参数名称 | 参数说明 |
class-pattern | 类名表达式匹配 |
method-pattern | 方法名表达式匹配 |
[d] | 展示每个方法的详细信息 |
[E] | 开启正则表达式匹配,默认为通配符匹配 |
[c:] | 指定 class 的 ClassLoader 的 hashcode |
[classLoaderClass:] | 指定执行表达式的 ClassLoader 的 class name |
[n:] | 具有详细信息的匹配类的最大数量(默认为 100) |
- sm java.lang.String # 显示String类加载的方法
- sm cn.phlos.csdn.demo.DemoController # 查看方法信息
- sm -d cn.phlos.csdn.demo.DemoController # 查看方法信息(详细信息-d)
jad 命令将 JVM 中实际运行的 class 的 byte code 反编译成 java 代码,便于你理解业务逻辑;
在 Arthas Console 上,反编译出来的源码是带语法高亮的,阅读更方便
当然,反编译出来的 java 代码可能会存在语法错误,但不影响你进行阅读理解
参数说明
参数名称 | 参数说明 |
class-pattern | 类名表达式匹配 |
[c:] | 类所属 ClassLoader 的 hashcode |
[classLoaderClass:] | 指定执行表达式的 ClassLoader 的 class name |
[E] | 开启正则表达式匹配,默认为通配符匹配 |
- # 反编译MathGame方法
- jad cn.phlos.csdn.demo.DemoController
- # 反编绎时只显示源代码(排除ClassLoader信息)。
- # 默认情况下,反编译结果里会带有ClassLoader信息,通过--source-only选项,可以只打印源代码。方便和mc/redefine命令结合使用。
- jad --source-only cn.phlos.csdn.demo.DemoController
- # 反编译到指定文件中
- jad --source-only cn.phlos.csdn.demo.DemoController > Demo.java
- # 只反编译DemoController类型中cost方法
- jad cn.phlos.csdn.demo.DemoController cost
Memory Compiler/内存编译器,编译.java文件生成.class。
- #在内存中编译 Test.java为Test.class
- mc /root/Demo.java
- #可以通过-d命令指定输出目录
- mc -d /root/output /root/Demo.java
推荐使用 retransform 命令
redefine 的 class 不能修改、添加、删除类的 field 和 method,包括方法参数、方法名称及返回值
如果 mc 失败,可以在本地开发环境编译好 class 文件,上传到目标系统,使用 redefine 热加载 class
目前 redefine 和 watch/trace/jad/tt 等命令冲突,以后重新实现 redefine 功能会解决此问题
注意, redefine 后的原来的类不能恢复,redefine 有可能失败(比如增加了新的 field),参考 jdk 本身的文档
1. reset命令对 redefine的类无效。如果想重置,需要 redefine原始的字节码。
2. redefine命令和 jad/ watch/ trace/ monitor/ tt等命令会冲突。执行完 redefine之后,如果再执行上面提到的命令,则会把 redefine的字节码重置。 原因是 jdk 本身 redefine 和 Retransform 是不同的机制,同时使用两种机制来更新字节码,只有最后修改的会生效。
redefine 的限制
不允许新增加 field/method
正在跑的函数,没有退出不能生效,比如下面新增加的System.out.println,只有run()函数里的会生效
参数说明
参数名称 | 参数说明 |
[c:] | ClassLoader 的 hashcode |
[classLoaderClass:] | 指定执行表达式的 ClassLoader 的 class name |
- # 1. 使用jad反编译DemoController输出到/root/Hello.java
- jad --source-only cn.phlos.csdn.demo.DemoController > /root/Hello.java
- # 2.按上面的代码编辑完毕以后,使用mc内存中对新的代码编译
- mc /root/Hello.java -d /root
- # 3.使用redefine命令加载新的字节码
- redefine /root/Hello.class
参数说明
参数名称 | 参数说明 |
class-pattern | 类名表达式匹配 |
[c:] | 类所属 ClassLoader 的 hashcode |
[classLoaderClass:] | 指定执行表达式的 ClassLoader 的 class name |
[d:] | 设置类文件的目标目录 |
[E] | 开启正则表达式匹配,默认为通配符匹配 |
- # 把String类的字节码文件保存到~/logs/arthas/classdump/目录下
- dump java.lang.String
- # 把demo包下所有的类的字节码文件保存到~/logs/arthas/classdump/目录下
- dump cn.*
查看 classloader 的继承树,urls,类加载信息
classloader 命令将 JVM 中所有的 classloader 的信息统计出来,并可以展示继承树,urls 等。
可以让指定的 classloader 去 getResources,打印出所有查找到的 resources 的 url。对于ResourceNotFoundException比较有用。
参数说明
参数名称 | 参数说明 |
[l] | 按类加载实例进行统计 |
[t] | 打印所有 ClassLoader 的继承树 |
[a] | 列出所有 ClassLoader 加载的类,请谨慎使用 |
[c:] | ClassLoader 的 hashcode |
[classLoaderClass:] | 指定执行表达式的 ClassLoader 的 class name |
[c: r:] | 用 ClassLoader 去查找 resource |
[c: load:] | 用 ClassLoader 去加载指定的类 |
案例1:
- #默认按类加载器的类型查看统计信息
- classloader
案例2:
- #按类加载器的实例查看统计信息,可以看到类加载的hashCode
- classloader -l
案例3:
- #查看ClassLoader的继承树
- classloader -t
案例4:
- # 通过类加载器的hashcode,查看此类加载器实际所在的位置
- classloader -c 349da6dd
案例5:
- #使用ClassLoader去查找指定资源resource所在的位置
- classloader -c 349da6dd -r META-INF/MANIFEST.MF
案例6:
- # 使用ClassLoader(该类的hashcode)去加载类
- classloader -c 70dea4e --load java.lang.String
classloader命令主要作用有哪些?
显示所有类加载器的信息
获取某个类加载器所在的jar包
获取某个资源在哪个jar包中
加载某个类
Arthas 目前支持 Web Console,用户在 attach 成功之后,可以直接访问:http://127.0.0.1:8563/
可以填入 IP,远程连接其它机器上的 arthas。
默认情况下,arthas 只 listen 127.0.0.1,所以如果想从远程连接,则可以使用 --target-ip参数指定 listen 的 IP,更多参考-h的帮助说明。 注意会有安全风险,考虑 Arthas Tunnel 的方案。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。