最近几天公司的服务器经常出现接口调用超时的情况,接口的状态一直是pending的状态,起初以为是程序的堆栈空间爆掉了,重启了项目并不好使,发现问题并没有这么简单,好在之前自己用过arthas,想起了这个工具,于是打算重拾arthas完成这次的bug排查。

首先去官网下载了最新的arthas,因为我用的java程序启动的,直接把整个包拖到服务器中,再使用java -jar启动起来就可以。之后会出现一排的java服务,前面会有很多序号,我们选择需要调试的java程序序号就可以进入该程序的内部,准备开始调优。

进入到下面这个页面就表示我们成功使用arthas连接到了java程序内

image-20230505212436246

arthas官网命令列表

https://arthas.aliyun.com/doc/commands.html

我先说几个常用的命令

  1. dashboard 查看控制台,这个命令可以让我们快速的浏览目前的程序情况,包括线程的状态,新生代老年代以及运行java程序的系统信息等等

    image-20230505212919528

  2. jvm 查看jvm的信息,这里信息太多只截了部分,例如线程部分从上到下依次是活跃数、守护线程活跃数、曾经的最大活跃数、总启动线程次数、当前死锁数

  3. thread 查看线程信息 使用thread -all查看所有线程信息。

    thread -b查看当前阻塞其他线程的线程,个人认为这个很有用,尤其是发生死锁的时候。thread -n 3 打印出当前最忙的前3个线程。thread ‘线程ID’ 查看该线程信息

    image-20230505213755720

  4. jad 反编译字节码文件 这个对于当前看不了源码的人还是很友好的,只要知道错误的类路径 使用jad 类路径就可以查看类的源码

    image-20230505214102760

  5. heapdump 生成hprof文件 这个就不多解释了 内存溢出或泄漏肯定要看的文件

  6. sc -d 类名 例如 sc -d com.sora.system.Admin 则可以获取到Admin这个类的信息 -d参数则表示

    输出当前类的详细信息,包括这个类所加载的原始文件来源、类的声明、加载的 ClassLoader 等详细信息。如果一个类被多个 ClassLoader 所加载,则会出现多次

    image-20231024143335156

继续说回线上排查bug的问题,因为公司是内网开发,只能文字描述😶。首先是用了dashboard命令,大致看了一下目前的堆栈情况,以及gc次数。发现没有异常。继续使用thread -all查看所有线程,果不其然,发现有好几个线程是阻塞的,为BLOCKED状态。继续使用jvm查看jvm信息,发现有3个死锁。有死锁就好办了,直接用thread -b找到死锁的根源,发现指向一个日志收集器,里面有一个OpenFeign远程调用的地方发生了死锁。至此,成功发现死锁的根源。

线上调试示例

这里我推荐安装一个插件,可以直接复制arthas的命令,就不需要记过多繁琐的命令了。

image-20231024143831485

动态执行线上方法并获取结果

有时我们想直接获取线上的方法返回结果,但本地没有环境,线上我们又没有账户去实时测试,这个时候我们可以使用vmtool命令。

这里我模拟出我们常用的三个情况,分别是无参的get请求、有多个参数的get请求、传递对象的post请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@GetMapping("select")
public Result selectUserList() {
List<Employee> employeeList = userMapper.selectList(Wrappers.lambdaQuery(Employee.class));
return Result.success(employeeList);
}

@GetMapping("select/{id}/{id2}")
public Result selectUserById(@PathVariable("id") Integer id,@PathVariable("id2") Integer id2) {
Employee employee = userMapper.selectById(id);
Employee employee2 = userMapper.selectById(id2);
return Result.success(Lists.newArrayList(employee2,employee));
}

@PostMapping("select/obj")
public Result selectUserByObcj(@RequestBody Employee employee) {
// 制定查询规则
LambdaQueryWrapper<Employee> wrapper = Wrappers.lambdaQuery(Employee.class)
.eq(Employee::getName, employee.getName())
.eq(Employee::getId, employee.getId());
List<Employee> employeeList = userMapper.selectList(wrapper);
return Result.success(employeeList);
}

首先我们先来说get请求,get请求很简单,我们直接在想要请求的方法名字上右键(注意必须是方法名上才会有提示)

image-20231024144224656

这个界面就是vmtool命令的配置界面,其中下面的classloader是我们传递对象参数时用的,get请求使用不到的,我们直接点击右下角复制命令。

image-20231024144248140

这里我们对命令进行一些简单的介绍,首先是-x 表示结果的展开层次,默认是1,这里我们改为3,可以看到更多信息;-className则是方法所在类

1
vmtool -x 3 --action getInstances --className com.sora.controller.UserController  --express 'instances[0].selectUserList()' 

在arthas界面直接粘贴执行,就可以获得这个方法的返回值

image-20231024144358687

多个参数的get请求也十分简单,我们直接复制命令,可以发现只是在方法参数中多了2个参数,我们直接将对应的占位符替换成我们的具体参数就可以,下面的命令表示查询id为1和2的员工对象。

1
vmtool -x 3 --action getInstances --className com.sora.controller.UserController  --express 'instances[0].selectUserById(1,2)'

最后重点说一下post,因为post请求需要传递参数,我们需要先获取到能表示参数唯一的classLoaderhash。这里我的参数是Employee类型的对象,所以我需要使用sc命令获取到该对象的classLoaderhash。

1
sc -d 类路径

image-20231024145341523

之后我们就获取到了post的命令,其中对象具体属性的赋值又分为两种,分别是全参构造方法赋值和set赋值。前者适合用于字段少且每个字段的值均使用到的情况,后者则更多用于从对象中抽取部分字段来使用。(当然你也可以在类中专门写一个只赋值部分字段的方法)

1
vmtool -x 3 --action getInstances --className com.sora.controller.UserController  --express 'instances[0].selectUserByObcj(new com.sora.domain.user.Employee())'  -c 1055e4af

首先我们来看第一种全参构造赋值,需要给所有的字段指定值

1
vmtool -x 3 --action getInstances --className com.sora.controller.UserController  --express 'instances[0].selectUserByObcj(new com.sora.domain.user.Employee(19,"19",null,null,null,null,null,null,null,null,null,null,null,null,null))'  -c 1055e4af

image-20231024153152400

第二种方法则为set赋值,我们创建一个空对象obj,并针对特定字段进行赋值(注意使用括号将整个过程包裹)

1
vmtool -x 3 --action getInstances --className com.sora.controller.UserController  --express 'instances[0].selectUserByObcj((#obj = new com.sora.domain.user.Employee(),#obj.setId(19),#obj.setName("19"),#obj))'  -c 1055e4af

image-20231024153209235