当我们的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 
正在跑的函数,没有退出不能生效 
 
快速入门 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 >= 1quit或者exit可以退出Arthas。
这里只是一个简单的示例,其他实际例子未完待续….
本文内容来自arthas官网文档