当我们的Java程序上线之后,有时候会由于未能及时发现自己写的bug,导致程序抽风,比如:CPU、内存持续升高,堆栈溢出等,这时候我们可以借助JVM在线诊断分析工具–Arthas 快速定位线上问题
Arthas (阿尔萨斯)是Alibaba开源的Java诊断工具,当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决:
这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
是否有一个全局视角来查看系统的运行状况?
有什么办法可以监控到JVM的实时运行状态?
怎么快速定位应用的热点,生成火焰图?
Arthas支持JDK 6+,支持Linux/Mac/Winodws,采用命令行交互模式,同时提供丰富的 Tab 自动补全功能,进一步方便进行问题的定位和诊断。
下载Arthas 首先我们需要去下载Arthas ,这里我下载的arthas-3.1.7-bin.zip ,解压后的文件目录 在该压缩包中包含了一个demo
程序用于上手练习,
常用命令 Arthas 基础命令:
help——查看命令帮助信息
cls——清空当前屏幕区域
session——查看当前会话的信息
reset ——重置增强类,将被 Arthas 增强过的类全部还原,Arthas 服务端关闭时会重置所有增强过的类
version——输出当前目标 Java 进程所加载的 Arthas 版本号
history——打印命令历史
quit——退出当前 Arthas 客户端,其他 Arthas 客户端不受影响
stop——关闭 Arthas 服务端,所有 Arthas 客户端全部退出
keymap ——Arthas快捷键列表及自定义快捷键
dashboard 当前系统的实时数据面板,按 ctrl+c 退出。当运行在Ali-tomcat时,会显示当前tomcat的实时信息,如HTTP请求的qps, rt, 错误数, 线程池信息等等。
列名
含义
ID
Java级别的线程ID,注意这个ID不能跟jstack中的nativeID一一对应
NAME
线程名
GROUP
线程组名
PRIORITY
线程优先级, 1~10之间的数字,越大表示优先级越高
STATE
线程的状态
%CPU
线程消耗的cpu占比,采样100ms,将所有线程在这100ms内的cpu使用量求和,再算出每个线程的cpu使用占比。
TIME
线程运行总时间,数据格式为分:秒
INTERRUPTED
线程当前的中断位状态
DAEMON
是否是daemon线程
thread 查看当前线程信息以及线程的堆栈
参数名称
参数说明
id
线程id
[n:]
指定最忙的前N个线程并打印堆栈
[b]
找出当前阻塞其他线程的线程
[i ]
指定cpu占比统计的采样间隔,单位为毫秒
thread:显示所有线程的信息
thread -n N:展示当前最忙的前N个线程并打印堆栈
thread 线程id: 显示指定线程的运行堆栈
thread -b: 找出当前阻塞其他线程的线程(有时候我们发现应用卡住了, 通常是由于某个线程拿住了某个锁, 并且其他线程都在等待这把锁造成的。 为了排查这类问题, arthas提供了thread -b, 一键找出那个罪魁祸首)
注意, 目前只支持找出synchronized关键字阻塞住的线程, 如果是java.util.concurrent.Lock, 目前还不支持。
thread -i: 指定采样时间间隔(thread -n 3 -i 1000)
thread –state:查看指定状态的线程
sc 查看JVM已加载的类信息,是”Search-Class”的简写,这个命令能搜索出所有已经加载到 JVM 中的 Class 信息,这个命令支持的参数有 [d]、[E]、[f] 和 [x:]。
参数名称
参数说明
class-pattern
类名表达式匹配
method-pattern
方法名表达式匹配
[d]
输出当前类的详细信息,包括这个类所加载的原始文件来源、类的声明、加载的ClassLoader等详细信息。
如果一个类被多个ClassLoader所加载,则会出现多次
[E]
开启正则表达式匹配,默认为通配符匹配
[f]
输出当前类的成员变量信息(需要配合参数-d一起使用)
[x:]
指定输出静态变量时属性的遍历深度,默认为 0,即直接使用 toString 输出
class-pattern支持全限定名,如com.taobao.test.AAA
,也支持com/taobao/test/AAA
这样的格式,这样,我们从异常堆栈里面把类名拷贝过来的时候,不需要在手动把/替换为.啦。
sc 默认开启了子类匹配功能,也就是说所有当前类的子类也会被搜索出来,想要精确的匹配,请打开options disable-sub-class true
开关
sc demo.*:模糊搜索
sc -d demo.MathGame:打印类的详细信息
sc -d -f demo.MathGame:打印出类的Field信息
jad 反编译指定已加载类的源码,jad 命令将 JVM 中实际运行的 class 的 byte code 反编译成 java 代码,便于你理解业务逻辑
参数名称
参数说明
class-pattern
类名表达式匹配
[c:]
类所属 ClassLoader 的 hashcode
[E]
开启正则表达式匹配,默认为通配符匹配
jad 包名.类名:反编译指定类
jad –source-only 包名.类名:反编绎时只显示源代码(默认情况下,反编译结果里会带有ClassLoader信息)
jad 包名.类名 函数名:反编译指定的函数
jad -c 包名.类名:反编译时指定ClassLoader
mc Memory Compiler/内存编译器,编译.java文件生成.class。
mc 类路径:编译.java文件生成.class
mc -c 类路径:可以通过-c参数指定classloader
mc -d [输出路径] 类路径:可以通过-d命令指定输出目录
注意,mc命令很大几率会失败。如果编译失败可以在本地编译好.class文件
redefine 加载外部的.class文件,redefine jvm已加载的类
注意, redefine后的原来的类不能恢复,redefine有可能失败(比如增加了新的field),参考jdk本身的文档。
redefine class文件路径:加载class到内存中
redefine -c class文件路径:指定classloader加载class到内存中
redefine的限制:
不允许新增加field/method
正在跑的函数,没有退出不能生效
快速入门 这个Jar包中只有MathGame.class
,这里先启动java -jar arthas-demo.jar
:
1 2 3 4 5 47086=2*13*1811 208=2*2*2*2*13 96301=23*53*79 illegalArgumentCount: 1, number is: -128441, need >= 2 illegalArgumentCount: 2, number is: -633, need >= 2
然后启动Arthas
,并连接到相应的程序进程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 E:\arthas-3.1.7-bin>java -jar arthas-boot.jar [INFO] arthas-boot version: 3.1.7 [INFO] Found existing java process, please choose one and hit RETURN. * [1]: 11588 finalshell.jar [2]: 8972 arthas-demo.jar 2 [INFO] arthas home: E:\arthas-3.1.7-bin [INFO] Try to attach process 8972 [INFO] Found java home from System Env JAVA_HOME: D:\Program Files\Java\jdk1.8.0_231 [INFO] Attach process 8972 success. [INFO] arthas-client connect 127.0.0.1 3658 ,---. ,------. ,--------.,--. ,--. ,---. ,---. / O \ | .--. ''--. .--'| '--' | / O \ ' .-' | .-. || '--'.' | | | .--. || .-. |`. `-. | | | || |\ \ | | | | | || | | |.-' | `--' `--'`--' '--' `--' `--' `--'`--' `--'`-----' wiki https://alibaba.github.io/arthas tutorials https://alibaba.github.io/arthas/arthas-tutorials version 3.1.7 pid 8972 time 2020-02-10 10:42:05 [arthas@8972]$
Arthas提供了dashboard
命令来查看当前进程的信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 ID NAME GROUP PRIORITY STATE %CPU TIME INTERRUPT DAEMON 12 AsyncAppender-Worker-arthas-c system 5 WAITING 0 0:0 false true 5 Attach Listener system 5 RUNNABLE 0 0:0 false true 3 Finalizer system 8 WAITING 0 0:0 false true 2 Reference Handler system 10 WAITING 0 0:0 false true 4 Signal Dispatcher system 9 RUNNABLE 0 0:0 false true 22 Timer-for-arthas-dashboard-ce system 10 RUNNABLE 0 0:0 false true 14 job-timeout system 5 TIMED_WAI 0 0:0 false true 1 main main 5 TIMED_WAI 0 0:0 false false 15 nioEventLoopGroup-2-1 system 10 RUNNABLE 0 0:0 false false 19 nioEventLoopGroup-2-2 system 10 RUNNABLE 0 0:0 false false 16 nioEventLoopGroup-3-1 system 10 RUNNABLE 0 0:0 false false 17 pool-1-thread-1 system 5 TIMED_WAI 0 0:0 false false 18 pool-2-thread-1 system 5 WAITING 0 0:0 false false Memory used total max usage GC heap 44M 145M 1694M 2.61% gc.ps_scavenge.count 4 ps_eden_space 26M 60M 625M 4.31% gc.ps_scavenge.time(ms) 29 ps_survivor_space 4M 5M 5M 99.38% gc.ps_marksweep.count 0 ps_old_gen 12M 80M 1271M 0.96% gc.ps_marksweep.time(ms) 0 nonheap 21M 22M -1 96.47% code_cache 5M 5M 240M 2.37% Runtime os.name Windows 10 os.version 10.0 java.version 1.8.0_231 java.home D:\Program Files\Java\jre1.8.0_231 systemload.average -1.00 processors 8 uptime 2519s
我们先使用thread 1 | grep 'main('
来查找main
所在的类
1 2 [arthas@8972]$ thread 1 | grep 'main(' at demo.MathGame.main(MathGame.java:17)
我们先反编译看看类的的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 [arthas@8972]$ jad --source-only demo.MathGame /* * Decompiled with CFR. */ package demo; import java.io.PrintStream; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.concurrent.TimeUnit; public class MathGame { private static Random random = new Random(); public int illegalArgumentCount = 0; public static void main(String[] args) throws InterruptedException { MathGame game = new MathGame(); do { game.run(); TimeUnit.SECONDS.sleep(1L); } while (true); } public void run() throws InterruptedException { try { int number = random.nextInt() / 10000; List<Integer> primeFactors = this.primeFactors(number); MathGame.print(number, primeFactors); } catch (Exception e) { System.out.println(String.format("illegalArgumentCount:%3d, ", this.illegalArgumentCount) + e.getMessage()); } } public static void print(int number, List<Integer> primeFactors) { StringBuffer sb = new StringBuffer(number + "="); for (int factor : primeFactors) { sb.append(factor).append('*'); } if (sb.charAt(sb.length() - 1) == '*') { sb.deleteCharAt(sb.length() - 1); } System.out.println(sb); } public List<Integer> primeFactors(int number) { if (number < 2) { ++this.illegalArgumentCount; throw new IllegalArgumentException("number is: " + number + ", need >= 2"); } ArrayList<Integer> result = new ArrayList<Integer>(); int i = 2; while (i <= number) { if (number % i == 0) { result.add(i); number /= i; i = 2; continue; } ++i; } return result; } }
这里我想在不停机的情况下修改一下判断条件,首先我先到处这个类的源代码:
1 jad --source-only demo.MathGame > D:/MathGame.java
修改一下逻辑:
1 2 3 4 if (number <=0) { ++this.illegalArgumentCount; throw new IllegalArgumentException("number is: " + number + ", need >= 1"); }
修改好之后,使用javac
将.java
编译成.class
,我们使用redefine
来加载类
1 2 [arthas@6780]$ redefine D:/MathGame.class redefine success, size: 1
可以看到最终效果:
1 2 3 4 5 illegalArgumentCount:3284, number is: -118046, need >= 1 illegalArgumentCount:3285, number is: -16511, need >= 1 illegalArgumentCount:3286, number is: -166238, need >= 1 112722=2*3*18787 10250=2*5*5*5*41
原来的need >= 2
变为了need >= 1
使用quit
或者exit
可以退出Arthas
。
这里只是一个简单的示例,其他实际例子未完待续….
本文内容来自arthas官网文档