说明:本文介绍线上问题排查利器,Arthas的使用及常用命令;
创建项目
首先,创建一个Demo项目,该项目有以下三个接口,对应三个问题:
-
test1:会造成CPU飙升,接近100%;
-
test2:会造成死锁;
-
test3:输出一段有错误拼写的信息;
(Controller代码)
@Autowired
private ArthasService arthasService;
/**
* 模拟CPU飙升
*
* @return
*/
@GetMapping({"/test1"})
public String test1() {
arthasService.cpuHigh();
return "cpuHigh";
}
/**
* 死锁问题
*
* @return
*/
@GetMapping({"/test2"})
public String test2() {
arthasService.deadThread();
return "deadThread";
}
/**
* 模拟热更新
*
* @return
*/
@GetMapping({"/test3"})
public String test3() {
return arthasService.updateOut();
}
(ServiceImpl代码)
import org.springframework.stereotype.Service;
@Service
public class ArthasService {
/**
* 模拟CPU飙升
*/
public void cpuHigh() {
new Thread(() -> {
while (true) {}
}).start();
}
/**
* 死锁问题
*/
public 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(1000L);
} 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(1000L);
} 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();
}
/**
* 模拟热更新
*
* @return
*/
public String updateOut() {
String name = "Helloo Arthas!";
return name;
}
}
将该Demo打成jar包,放到服务器上运行:
nohup java -jar arthas-demo.jar > output.log &
浏览器访问,没得问题
通过top
命令可以查看到该程序占用CPU资源很高;
Arthas下载和启动
这时,可通过Arthas工具对上面的项目进行问题排查,使用如下:
(1)下载
Arthas官网(https://arthas.aliyun.com/),在服务器上敲下面的命令将arthas的jar包下载下来
curl -O https://arthas.aliyun.com/math-game.jar
可以在当前目录下看到 arthas-boot.jar
文件
(2)启动
通过java命令启动Arthas
java -jar arthas-boot.jar
它会检测到当前正在运行的java项目,并罗列出来,可敲前面的序号针对某个项目使用Arthas,如上图,我只有一个,敲1
即可,这样就进入了Arthas的操作命令行。
接下来,就可以针对我们前面启动的项目进行一系列操作。
Arthas常用命令
dashboard
说明:查看当前系统的实时数据面板,默认每5秒刷新一次。
可配合的参数有:
-
-i:刷新实时数据的时间间隔(ms),默认5000ms;
-
-n:刷新实时数据的次数;
thread
说明:查看当前线程信息,查看线程的堆栈。
可配合的参数有:
-
id:线程ID,dashboard上面一栏的第一列数值;
-
-b:找出当前阻塞其他线程的线程;
排查问题一
通过以上两个命令,我们可以排查出前面项目中的CPU飙升和死锁的问题,如下:
敲入dashboard
命令后,浏览器访问/test1
接口
观察数据面板,发现有一个线程CPU占用率极高;
Ctrl + C
停止数据面板,敲thread 进程id
,查看该线程信息;
该类的第13行代码导致CPU飙升,此时可以进入IDEA中排查问题。
注释掉这段死循环代码,重新打包上传;
排查问题二
问题二是死锁问题,同样的,先敲dashboard
调出数据面板,然后访问/test2
接口,观察数据变化;
可以看到,存在BLOCKED
已阻塞的线程;
使用thread id
命令查看该线程信息,也可使用thread -b
查看阻塞线程的信息;
可查到造成阻塞线程的代码位置;
watch
说明:函数执行数据观测,使用该命令可以观测方法相关情况,如方法的参数、返回值、异常信息等;
配合该命令的相关参数如下:
如下,观测该类中的cpuHigh()方法,深度为2;
watch com.example.arthasdemo.service.ArthasService cpuHigh -x 2
接着去浏览器上访问对应的接口,会打印如下信息:
有调用的类、方法数量及用时,返回值等信息;
此时,修改一下项目代码,在第三个接口前面手动制造一个异常,如下:
/**
* 模拟热更新
*
* @return
*/
public String updateOut() {
// 手动制造一个异常
System.out.println(1/0);
String name = "Helloo Arthas!";
return name;
}
重新打包,上传到服务器上运行,敲下面的命令可观察对应方法抛出的异常信息;
watch com.example.arthasdemo.service.ArthasService updateOut '{throwExp}' -e -x 2
浏览器上访问对应的接口,观察Arthas打印的信息:
打印了异常信息,该命令可用于线上项目的BUG排查;
trace
说明:可打印方法内部调用路径,并输出方法路径上的每个节点上耗时;
如下,打印/test1
接口的信息:
trace com.example.arthasdemo.controller.TestController test1
浏览器访问test1接口,该命令会打印出该接口的调用路径,以及各个节点的耗时,该命令可用于接口优化;
另外,配合#cost
参数可找出在对应耗时范围内的节点,如下,找出耗时在0.1ms以上的节点;
jad
说明:反编译指定已加载类的源码;
项目打成jar后都是class文件,通过该命令可将线上项目的class文件反编译为java文件。如下,将对应的类反编译为java文件,箭头右侧是反编译后的文件路径及文件名:
jad com.example.arthasdemo.service.ArthasService > /home/ArthasService.java
可使用cat命令打开该文件,可以查看反编译后的源码文件,但文件中除了源码还有一些类加载器信息和附带的代码行数,是不能编译通过的
可在反编译时加上这两个参数,--source-only
和 --lineNumber false
得到纯净的源码。前者表示只要源码,后者表示不加行号。
sc
说明:查看 JVM 已加载的类信息,-d
可输出当前类的详细信息;
如下,查看某类的详细信息:
sc -d com.example.arthasdemo.service.ArthasService
注意最后一行信息,类加载器的Hash值,后面指定编译器编译某类的java文件时可用到;
mc
说明:编译.java文件生成.class;
配合的参数如下:
-
-c:指定类加载器,可使用前的sc命令查看;
-
-d:编译后的目录;
将前面反编译的java文件,再编译成class文件,如下:
mc -c 1593948d /home/ArthasService.java -d /home
class文件直接打开无法识别,可在对应的目录下找到该class文件;
redefine
说明:加载外部的.class文件;
如下,加载前面mc命令
产生的class文件:
redefine /home/com/example/arthasdemo/service/ArthasService.class
-c
可指定类加载器,和前面的mc一样;
排查问题三
jad命令
、mc命令
和redefine命令
配合使用,可实现线上项目热更新,对/test3
接口打印的错误英文信息进行临时修复。
首先,使用jad命令
反编译对应的类,然后使用vim
命令修改源码,或者下载到本地使用编辑工具修改;
修改内容:Helloo Arthas! ⇒ Hello Arthas!
其次,使用sc命令
、mc命令
编译修改后的java文件,成class文件;
最后,使用redefine命令
加载修改源码后的class文件,实现在线更新;
此时,再访问/test3
接口,打印的信息变成了我们修改后的内容;
但是,redefine命令
有限制条件,有的修改内容即使加载也不会生效,官方文档说明如下:
其他命令
除了上面一些功能命令,还有一些其他辅助命令,如下:
-
cat:查看文件;
-
cls:清屏;
-
stop:关闭 Arthas 服务端;
-
exit:退出当前 Arthas 客户端,其他 Arthas 客户端不受影响。;
-
history:打印命令历史
总结
本文介绍了Arthas的使用及一些常用方法,更丰富的命令及使用,强烈建议大家去官网中阅读文档(https://arthas.aliyun.com/doc/)