1 Arthas 介绍
1.1 Arthas是什么
Arthas 是Alibaba开源的Java诊断工具。它支持JDK 6+,支持Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 Tab 自动补全功能,
进一步方便进行问题的定位和诊断。
Arthas 官方文档十分详细,本文也参考了官方文档内容,同时在开源在的 Github 的项目里的 Issues 里不仅有问题反馈,更有大量的使用案例,也可以进行学习参考。
开源地址:https://github.com/alibaba/arthas
官方文档:https://alibaba.github.io/arthas
1.2 Arthas使用场景
当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决:
-
这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
-
我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
-
遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
-
线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
-
是否有一个全局视角来查看系统的运行状况?
-
有什么办法可以监控到JVM的实时运行状态?
-
怎么快速定位应用的热点,生成火焰图?
1.3 Arthas怎么用
Arthas 是一款命令行交互模式的 Java 诊断工具,由于是 Java 编写,所以可以直接下载相应 的 jar 包运行。
1.3.1 standalone
- wget https://alibaba.github.io/arthas/arthas-boot.jar
- java -jar arthas-boot.jar执行后,选择需要检测的应用进程id即可
1.3.2 idea plugin
- 以idea为例,在应用市场搜索Alibaba Cloud View并点击安装
- 添加需要检测的host
- 点击more-dignostic,等待一会后,安装成功,选择需要检测的应用进程id即可
2 Arthas 基本使用篇
在了解了什么是 Arthas,以及 Arthas 的启动方式,下面详细介绍 Arthas 的常用使用方式。在使用命令的过程中如果有问题,每个命令都可以是 -h 查看帮助信息。
首先编写一个有各种情况的测试类运行起来,再使用 Arthas 进行问题定位。下面代码模拟了cpu过高,线程阻塞以及死锁。完整代码可以访问https://github.com/pj1987111/hongyinotes/tree/main/hongyiarthas获取。
@Slf4j
public class Problems {
private static int CPU_THREADS = 10;
private static ExecutorService executorService = Executors.newFixedThreadPool(CPU_THREADS);
public static void start() {
// 模拟 CPU 过高
cpu();
// 模拟线程阻塞
thread();
// 模拟线程死锁
deadThread();
}
/**
* 模拟cpu高损耗
*/
private static void cpu() {
for (int i = 0; i < CPU_THREADS; i++) {
executorService.submit(new Thread(() -> {
while (true) {
cpurun();
}
}));
}
}
/**
* 只有完整退出的方法才可以被热更新
*/
private static void cpurun() {
log.info("cpu start");
// try {
// Thread.sleep(10000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
}
/**
* 模拟线程阻塞,向已经满了的线程池提交线程
*/
private static void thread() {
// 添加到线程
executorService.submit(new Thread(() -> {
while (true) {
log.debug("thread start");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}));
}
/**
* 死锁
*/
private static void deadThread() {
Object resourceA = new Object();
Object resourceB = new Object();
Thread threadA = new Thread(() -> {
synchronized (resourceA) {
log.info(Thread.currentThread() + " get ResourceA");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info(Thread.currentThread() + "waiting get resourceB");
synchronized (resourceB) {
log.info(Thread.currentThread() + " get resourceB");
}
}
});
Thread threadB = new Thread(() -> {
synchronized (resourceB) {
log.info(Thread.currentThread() + " get ResourceB");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info(Thread.currentThread() + "waiting get resourceA");
synchronized (resourceA) {
log.info(Thread.currentThread() + " get resourceA");
}
}
});
threadA.start();
threadB.start();
}
}
2.1 全局监控
首先可以执行dashboard
命令,可以监控内存,GC,线程,运行环境等,如下图所示。
2.2 线程状态监控
2.2.1 CPU 状态监控
根据代码,可以看到cpu()方法是一个死循环打印,非常占用cpu。
输入thread
查看线程的CPU占用情况,将通过CPU占比倒序输出线程情况,可以看到前10个线程总计cpu占用100%。
[arthas@3599812]$ thread
Threads Total: 41, NEW: 0, RUNNABLE: 12, BLOCKED: 2, WAITING: 23, TIMED_WAITING: 4, TERMINATED: 0
ID NAME GROUP PRIORITY STATE %CPU TIME INTERRUPTED DAEMON
33 pool-1-thread-2 main 5 RUNNABLE 10 0:18 false false
43 pool-1-thread-7 main 5 WAITING 10 0:18 false false
45 pool-1-thread-8 main 5 WAITING 10 0:18 false false
47 pool-1-thread-9 main 5 WAITING 10 0:18 false false ```
然后可以输入thread 线程id
查看线程的堆栈信息,可以迅速定位到高cpu损耗的代码位置。
[arthas@3599812]$ thread 33
"pool-1-thread-2" Id=33 WAITING on java.util.concurrent.locks.ReentrantLock$NonfairSync@77efb74e
at sun.misc.Unsafe.park(Native Method)
- waiting on java.util.concurrent.locks.ReentrantLock$NonfairSync@77efb74e
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:209)
at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
at ch.qos.logback.core.OutputStreamAppender.writeBytes(OutputStreamAppender.java:197)
at ch.qos.logback.core.OutputStreamAppender.subAppend(OutputStreamAppender.java:231)
at ch.qos.logback.core.OutputStreamAppender.append(OutputStreamAppender.java:102)
at ch.qos.logback.core.UnsynchronizedAppenderBase.doAppend(UnsynchronizedAppenderBase.java:84)
at ch.qos.logback.core.spi.AppenderAttachableImpl.appendLoopOnAppenders(AppenderAttachableImpl.java:51)
at ch.qos.logback.classic.Logger.appendLoopOnAppenders(Logger.java:270)
at ch.qos.logback.classic.Logger.callAppenders(Logger.java:257)