记一次线上使用arthas排查Bug的经验
最近几天公司的服务器经常出现接口调用超时的情况,接口的状态一直是pending的状态,起初以为是程序的堆栈空间爆掉了,重启了项目并不好使,发现问题并没有这么简单,好在之前自己用过arthas,想起了这个工具,于是打算重拾arthas完成这次的bug排查。
首先去官网下载了最新的arthas,因为我用的java程序启动的,直接把整个包拖到服务器中,再使用java -jar启动起来就可以。之后会出现一排的java服务,前面会有很多序号,我们选择需要调试的java程序序号就可以进入该程序的内部,准备开始调优。
进入到下面这个页面就表示我们成功使用arthas连接到了java程序内
arthas官网命令列表
我先说几个常用的命令
dashboard 查看控制台,这个命令可以让我们快速的浏览目前的程序情况,包括线程的状态,新生代老年代以及运行java程序的系统信息等等
jvm 查看jvm的信息,这里信息太多只截了部分,例如线程部分从上到下依次是活跃数、守护线程活跃数、曾经的最大活跃数、总启动线程次数、当前死锁数
thread 查看线程信息 使用thread -all查看所有线程信息。
thread -b查看当前阻塞其他线程的线程,个人认为这个很有用,尤其是发生死锁的时候。thread -n 3 打印出当前最忙的前3个线程。thread ‘线程ID’ 查看该线程信息
jad 反编译字节码文件 这个对于当前看不了源码的人还是很友好的,只要知道错误的类路径 使用jad 类路径就可以查看类的源码
heapdump 生成hprof文件 这个就不多解释了 内存溢出或泄漏肯定要看的文件
sc -d 类名 例如 sc -d com.sora.system.Admin 则可以获取到Admin这个类的信息 -d参数则表示
输出当前类的详细信息,包括这个类所加载的原始文件来源、类的声明、加载的 ClassLoader 等详细信息。如果一个类被多个 ClassLoader 所加载,则会出现多次
继续说回线上排查bug的问题,因为公司是内网开发,只能文字描述😶。首先是用了dashboard命令,大致看了一下目前的堆栈情况,以及gc次数。发现没有异常。继续使用thread -all查看所有线程,果不其然,发现有好几个线程是阻塞的,为BLOCKED状态。继续使用jvm查看jvm信息,发现有3个死锁。有死锁就好办了,直接用thread -b找到死锁的根源,发现指向一个日志收集器,里面有一个OpenFeign远程调用的地方发生了死锁。至此,成功发现死锁的根源。
线上调试示例
这里我推荐安装一个插件,可以直接复制arthas的命令,就不需要记过多繁琐的命令了。
动态执行线上方法并获取结果
有时我们想直接获取线上的方法返回结果,但本地没有环境,线上我们又没有账户去实时测试,这个时候我们可以使用vmtool命令。
这里我模拟出我们常用的三个情况,分别是无参的get请求、有多个参数的get请求、传递对象的post请求。
1 |
|
首先我们先来说get请求,get请求很简单,我们直接在想要请求的方法名字上右键(注意必须是方法名上才会有提示)
这个界面就是vmtool命令的配置界面,其中下面的classloader是我们传递对象参数时用的,get请求使用不到的,我们直接点击右下角复制命令。
这里我们对命令进行一些简单的介绍,首先是-x 表示结果的展开层次,默认是1,这里我们改为3,可以看到更多信息;-className则是方法所在类
1 | vmtool -x 3 --action getInstances --className com.sora.controller.UserController --express 'instances[0].selectUserList()' |
在arthas界面直接粘贴执行,就可以获得这个方法的返回值
多个参数的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 类路径 |
之后我们就获取到了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 |
第二种方法则为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 |