1. Arthas是什么
Arthas(阿尔萨斯)是一款由阿里巴巴开源团队开发的Java应用性能监控与诊断工具。它作为一种开源的Java诊断工具,主要用于在生产环境中实时监控、分析和诊断Java应用程序的性能问题。Arthas提供了一系列的命令行工具,可以实时查看Java应用的运行状态、堆栈信息、方法执行耗时等关键性能数据,帮助开发者快速定位并解决问题。
Arthas适用于各种Java应用场景,特别是在生产环境中解决实时性能问题。以下是一些常见的使用场景:
Arthas可以实时监控应用程序的性能数据,包括方法执行时间、线程状态等,帮助开发者快速定位潜在的性能瓶颈。
在生产环境中,使用Arthas可以实时进行代码调试,查看变量的值、修改变量的值,甚至动态加载类,而不需要重启应用。
Arthas支持对Java应用的内存进行实时分析,帮助开发者查找内存泄漏、优化内存使用等问题。
Arthas可以在生产环境中实时诊断问题,无需重启应用,从而快速定位线上故障,提高故障排查效率。
Arthas的使用相对简单,主要通过命令行工具进行操作。以下是一些基本的使用步骤:
首先,通过官方网站或Maven仓库下载Arthas,并解压到本地目录。
官方文档:https://alibaba.github.io/arthas
到官方的开源地址:https://github.com/alibaba/arthas,或者国内的Gitee地址下去下载arthas的jar包。
# github下载
wget https://alibaba.github.io/arthas/arthas-boot.jar
# 或者 Gitee 下载
wget https://arthas.gitee.io/arthas-boot.jar
# 打印帮助信息
java -jar arthas-boot.jar -h
Arthas 只是一个 java 程序,所以可以直接用 java -jar
运行。运行时或者运行之后要选择要监测的 Java 进程。
# 运行方式1,先运行,在选择 Java 进程 PID
java -jar arthas-boot.jar
# 选择进程(输入[]内编号(不是PID)回车)
[INFO] arthas-boot version: 3.5.0
[INFO] Found existing java process, please choose one and hit RETURN.
* [1]: 24480 com.Arthas
[2]: 20520 com.jay.demos.ArthasTest
[3]: 16200 org.jetbrains.jps.cmdline.Launcher
[4]: 21032 org.jetbrains.idea.maven.server.RemoteMavenServer
# 运行方式2,运行时选择 Java 进程 PID
java -jar arthas-boot.jar [PID]
一旦连接成功,可以使用各种Arthas命令进行实时监控、诊断,例如:dashboard
查看仪表盘、trace
追踪方法调用、watch
监控变量等。下面列举一些常用命令。
命令 | 介绍 |
---|---|
dashboard | 当前系统的实时数据面板 |
thread | 查看当前 JVM 的线程堆栈信息 |
watch | 方法执行数据观测 |
trace | 方法内部调用路径,并输出方法路径上的每个节点上耗时 |
stack | 输出当前方法被调用的调用路径 |
tt | 方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测 |
monitor | 方法执行监控 |
jvm | 查看当前 JVM 信息 |
vmoption | 查看,更新 JVM 诊断相关的参数 |
sc | 查看 JVM 已加载的类信息 |
sm | 查看已加载类的方法信息 |
jad | 反编译指定已加载类的源码 |
classloader | 查看 classloader 的继承树,urls,类加载信息 |
heapdump | 类似 jmap 命令的 heap dump 功能 |
使用 shutdown 退出时 Arthas 同时自动重置所有增强过的类 。
上面已经了解了Arthas的使用场景以及启动方式,下面就来说说Arthas的使用方式。
首先,编写一个有各种异常场景的代码。这个代码模拟了CPU过高,线程阻塞,线程死锁,内存不断被消耗等场景。
将代码上传到Linux服务器上,通过 1. javac ArthasTest.java
命令编译将ArthasTest.java文件编译成ArthasTest.class文件。接着通过 java ArthasTest
命令来运行此文件。
import java.util.HashSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* <p>
* Arthas Demo
* 公众号:码农飞哥
*
* @Author 码农飞哥
*/
public class ArthasTest {
private static HashSet hashSet = new HashSet();
/**
* 线程池,大小1
*/
private static ExecutorService executorService = Executors.newFixedThreadPool(1);
public static void main(String[] args) {
// 模拟 CPU 过高
cpu();
// 模拟线程阻塞
thread();
// 模拟线程死锁
deadThread();
// 不断的向 hashSet 集合增加数据
addHashSetThread();
}
/**
* 不断的向 hashSet 集合添加数据
*/
public static void addHashSetThread() {
// 初始化常量
new Thread(() -> {
int count = 0;
while (true) {
try {
hashSet.add("count" + count);
Thread.sleep(10000);
count++;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
public static void cpu() {
cpuHigh();
cpuNormal();
}
/**
* 极度消耗CPU的线程
*/
private static void cpuHigh() {
Thread thread = new Thread(() -> {
while (true) {
System.out.println("极度消耗CPU的线程,任务死循环,cpu start 100");
}
});
// 添加到线程
executorService.submit(thread);
}
/**
* 普通消耗CPU的线程
*/
private static void cpuNormal() {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
while (true) {
System.out.println("普通消耗CPU的线程,任务睡眠3000毫秒,死循环,cpu start");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
/**
* 模拟线程阻塞,向已经满了的线程池提交线程
*/
private static void thread() {
Thread thread = new Thread(() -> {
while (true) {
System.out.println("模拟线程阻塞,thread start");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// 添加到线程
executorService.submit(thread);
}
/**
* 死锁
*/
private static 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();
}
}
首先通过 dashboard 命令查看当前系统的实时数据面板。结果如下,我们看到有两个线程的状态是BLOCKED状态。
上面的代码例子有一个 CPU
空转的死循环,非常的消耗 CPU性能
,那么怎么找出来呢?
使用 thread查看所有线程信息,同时会列出每个线程的 CPU
使用率,可以看到图里 ID 为12 的线程 CPU 使用100%。
使用命令 thread 12 查看 CPU 消耗较高的 12 号线程信息,可以看到 CPU 使用较高的方法和行数。
上面是通过先观察总体的线程信息,然后查看具体的线程运行情况,如果只是为了寻找CPU使用较高的线程,那么可以通过 thread -n[显示的线程个数]
,就可以排列出 CPU 使用率 Top N 的线程。
在介绍线程死锁之前,首先回顾一下线程的几种常见状态:
上面的模拟代码里,定义了线程池大小为1 的线程池,然后在 cpuHigh
方法里提交了一个线程,在 thread
方法再次提交了一个线程,后面的这个线程因为线程池已满,会阻塞下来。
使用 thread | grep pool 命令查看线程池里线程信息。
上面的模拟代码里 deadThread
方法实现了一个死锁,使用 thread -b 命令查看直接定位到死锁信息。
/**
* 死锁
*/
private static 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();
}
通过 jad ArthasTest 命令可以反编译代码。
通过sm命令可以查看类中的所有方法信息。
通过ognl ‘@ArthasTest@hashSet’ 命令可以查看 ArthasTest类的hashSet 静态变量的值。
先定义一个测试的方法,这里定义了UserController类以及UserServiceImpl类,UserController类作为接口的总入口。
import java.util.HashMap;
@RestController
@Slf4j
public class UserController {
@Autowired
private UserServiceImpl userService;
@GetMapping(value = "/user")
public HashMap<String, Object> getUser(Integer uid) throws Exception {
log.info("------模拟用户查询,uid={}----", uid);
// 模拟用户查询
userService.get(uid);
HashMap<String, Object> hashMap = new HashMap<>();
hashMap.put("uid", uid);
hashMap.put("name", "name" + uid);
return hashMap;
}
}
UserServiceImpl类作为业务实现。
package com.jay.demos.web;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class UserServiceImpl {
public void get(Integer uid) throws Exception {
check(uid);
service(uid);
redis(uid);
mysql(uid);
}
public void service(Integer uid) throws Exception {
int count = 0;
for (int i = 0; i < 10; i++) {
count += i;
}
log.info("service end {}", count);
}
public void redis(Integer uid) throws Exception {
int count = 0;
for (int i = 0; i < 10000; i++) {
count += i;
}
log.info("redis end {}", count);
}
public void mysql(Integer uid) throws Exception {
long count = 0;
for (int i = 0; i < 10000000; i++) {
count += i;
}
log.info("mysql end {}", count);
}
public boolean check(Integer uid) throws Exception {
if (uid == null || uid < 0) {
log.error("uid不正确,uid:{}", uid);
throw new Exception("uid不正确");
}
return true;
}
}
如果要通过一个接口中,各部分的耗时,则可以使用: trace [类地址] [方法名] 。例如本例中的就是 trace com.jay.demos.web.UserController getUser