1. 概述概述
Arthas(阿尔萨斯) 能做什么?
Arthas 是Alibaba开源的Java诊断工具,深受开发者喜爱。当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决:
- 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
- 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
- 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
- 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
- 是否有一个全局视角来查看系统的运行状况?
- 有什么办法可以监控到JVM的实时运行状态?
- 怎么快速定位应用的热点,生成火焰图?
怎么做的?
Arthas 是基于 ASM 和 Java Agent 技术实现的 Java 诊断利器。
ASM 是指一个 Java 字节码操作框架,用于动态生成或者增强 class。
采用 Attach API 方式的 Java Agent 是指在 JVM 启动后通过 Attach API 执行 agentmian 方法,利用 Instrumentation API 的 debug 和 profiler 能力。
-
快速入门
1. 准备案例代码
以下是一个简单的Java程序,每隔一秒生成一个随机数,再执行质因数分解,并打印出分解结果。代码 的内容不用理会这不是现在关注的点。
package demo;
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();
//用于统计生成的不合法变量的个数
private int illegalArgumentCount = 0;
public static void main(String[] args) throws InterruptedException {
MathGame game = new MathGame();
//死循环,每过 1 秒调用 1 次下面的方法(不是开启一个线程)
while (true) {
game.run();
TimeUnit.SECONDS.sleep(1);
}
}
//打印质因数分解的结果
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 void run() throws InterruptedException {
try {
//随机生成 1 万以内的整数
int number = random.nextInt() / 10000;
//调用方法进行质因数分解
List<Integer> primeFactors = primeFactors(number);
//打印结果
print(number, primeFactors);
} catch (Exception e) {
System.out.println(String.format("illegalArgumentCount:%3d, ", illegalArgumentCount) + e.getMessage());
}
}
//计算number的质因数分解
public List<Integer> primeFactors(int number) {
//如果小于 2 ,则抛出异常,并且计数加 1
if (number < 2) {
illegalArgumentCount++;
throw new IllegalArgumentException("number is: " + number + ", need >= 2");
}
//用于保存每个质数
List<Integer> result = new ArrayList<Integer>();
int i = 2;
while (i <= number) { //如果i大于number就退出循环
//能整除,则i为一个因数,number为整除的结果再继续从 2 开始
if (number % i == 0) {
result.add(i);
number = number / i;
i = 2;
} else {
i++;
}
}
return result;
}
}
- 启动项目
- 启动Arthas java -jar arthas-boot.jar
- 选择要粘附的进程
- 如果端口号被占用,也可以通过以下命令换成另一个端口号执行
- 支持通过浏览器连接 arthas (http://127.0.0.1:3658/)
java -jar arthas-boot.jar --telnet-port 9998 --http-port -1
-
常用命令
-
dashboard 仪表板
- 第一部分是显示JVM中运行的所有线程:所在线程组,优先级,线程的状态,CPU的占用率,是否是后台进程等
- 第二部分显示的JVM内存的使用情况
- 第三部分是操作系统的一些信息和Java版本号
-
通过 thread 命令来获取到线程信息
-
通过 jad 来反编译类
jad demo.MathGame --source-only
-
通过watch命令来查看函数的入参和返回值
watch demo.MathGame primeFactors '{params,returnObj,throwExp}' -n 5 -x 3
-
退出 arthas
- 如果只是退出当前的连接,可以用quit或者exit命令。Attach到目标进程上的arthas还会继续运行,端口会保持开放,下次连接时可以直接连接上。
- 如果想完全退出arthas,可以执行stop命令。
无法复制加载中的内容
-
JVM相关排查
-
dashboard 显示当前系统的实时数据面板
- ID: Java级别的线程ID,注意这个ID不能跟jstack中的nativeID一一对应
- NAME: 线程名
- GROUP: 线程组名
- PRIORITY: 线程优先级, 1~10之间的数字,越大表示优先级越高
- STATE: 线程的状态
- CPU%: 线程消耗的cpu占比,采样100ms,将所有线程在这100ms内的cpu使用量求和,再算出每个线程的cpu使用占比。
- TIME: 线程运行总时间,数据格式为分:秒
- INTERRUPTED: 线程当前的中断位状态
- DAEMON: 是否是daemon线程
-
thread 查看当前 JVM 的线程堆栈信息
# 展示当前最忙的前 3 个线程并打印堆栈
thread -n 3
# 当没有参数时,显示所有线程的信息
thread
# 显示 1 号线程的运行堆栈
thread 1
# 找出当前阻塞其他线程的线程,有时候我们发现应用卡住了, 通常是由于某个线程拿住了某个锁, 并且其他线程都在等待这把锁造成的。 为了排查这类问题, arthas提供了thread -b, 一键找出死锁原因。
thread -b
# 指定采样时间间隔,每过1000毫秒采样,显示最占时间的3个线程
thread -i 1000 -n 3
# 查看处于等待状态的线程
thread --state WAITING
-
jvm 查看当前 JVM 的信息
- COUNT: JVM当前活跃的线程数
- DAEMON-COUNT: JVM当前活跃的守护线程数
- PEAK-COUNT: 从JVM启动开始曾经活着的最大线程数
- STARTED-COUNT: 从JVM启动开始总共启动过的线程次数
- DEADLOCK-COUNT: JVM当前死锁的线程数
- MAX-FILE-DESCRIPTOR-COUNT:JVM进程最大可以打开的文件描述符数
- OPEN-FILE-DESCRIPTOR-COUNT:JVM当前打开的文件描述符数
-
vmoption查看和设置系统参数
# 查看所有的选项
vmoption
# 查看指定的选项
vmoption PrintGCDetails
# 更新指定的选项
vmoption PrintGCDetails true
-
实战使用monitor/watch/trace/stack相关
-
Monitor
- monitor 命令是一个非实时返回命令,实时返回命令是输入之后立即返回,而非实时返回的命令,则是不断的等待目标 Java 进程返回信息,直到用户输入 Ctrl+C 为止。
- 作用是监视一个时间段中指定方法的执行次数,成功次数,失败次数,耗时等这些信息
无法复制加载中的内容
# 过 5 秒统计一次,统计类demo.MathGame中primeFactors方法
monitor -c 5 demo.MathGame primeFactors
-
Watch
watch 方法执行数据观测,让你能方便的观察到指定方法的调用情况。 能观察到的范围为:返回值、抛出异常、入参,通过编写OGNL 表达式进行对应变量的查看。
无法复制加载中的内容
# 观察demo.MathGame类中primeFactors方法出参和返回值,结果属性遍历深度为 2 。params表示所有参数数组,returnObject表示返回值
watch demo.MathGame primeFactors "{params,returnObj}" -x 2
# 观察方法入参,对比前一个例子,返回值为空(事件点为方法执行前,因此获取不到返回值)
watch demo.MathGame primeFactors "{params,returnObj}" -x 2 -b
# 同时观察方法调用前和方法返回后,参数里-n 2,表示只执行两次。这里输出结果中,第一次输出的是方法调用前的观察表达式的结果,第二次输出的是方法返回后的表达式的结果params表示参数,target表示执行方法的对象,returnObject表示返回值
watch demo.MathGame primeFactors "{params,target,returnObj}" -x 2 -b -s -n 2
# 观察当前对象中的属性,如果想查看方法运行前后,当前对象中的属性,可以使用target关键字,代表当前对象
watch demo.MathGame primeFactors 'target'
# 条件表达式的例子,输出第 1 参数小于的情况
watch demo.MathGame primeFactors "{params[0],target}" "params[0]<0"
-
Trace
无法复制加载中的内容
- 方法内部调用路径,并输出方法路径上的每个节点上耗时
- trace 命令能主动搜索 class-pattern/method-pattern 对应的方法调用路径,渲染和统计整个调用链路上的所有性能开销和追踪调用链路。
# trace函数指定类的指定方法
trace demo.MathGame run
# 如果方法调用的次数很多,那么可以用-n参数指定捕捉结果的次数。比如下面的例子里,捕捉到一次调用就退出命令。
trace demo.MathGame run -n 1
# 默认情况下,trace不会包含jdk里的函数调用,如果希望trace jdk里的函数,需要显式设置--skipJDKMethod false。
trace --skipJDKMethod false demo.MathGame run
# 据调用耗时过滤,trace大于0.5ms的调用路径
trace demo.MathGame run '#cost > .5'
-
Stack
- 输出当前方法被调用的调用路径
- 很多时候我们都知道一个方法被执行,但这个方法被执行的路径非常多,或者你根本就不知道这个方法是从那里被执行了,此时你需要的是 stack 命令。
无法复制加载中的内容
# 获取primeFactors的调用路径
stack demo.MathGame primeFactors
# 条件表达式来过滤,第 0 个参数的值小于 0 ,-n表示获取 2 次
stack demo.MathGame primeFactors 'params[0]<0' -n 2
# 据执行时间来过滤,耗时大于 5 毫秒
stack demo.MathGame primeFactors '#cost>5'
-
Profiler
- profiler 命令支持生成应用热点的火焰图。本质上是通过不断的采样,然后把收集到的采样结果生成火焰图。
- 命令基本运行结构是 profiler 命令 [命令参数]
无法复制加载中的内容
# 启动 profiler
profiler start
# 显示支持的事件
profiler list
# 停止 profiler 生成 svg 格式结果
profiler stop
# 默认情况下,结果文件是svg格式,如果想生成html格式,可以用--format参数指定:或者在--file参数里用文件名指名格式。比如--file /tmp/result.html
profiler stop --format html
-
火焰图是基于 perf 结果产生的SVG 图片,用来展示 CPU 的调用栈。
- y 轴表示调用栈,每一层都是一个函数。调用栈越深,火焰就越高,顶部就是正在执行的函数,下方都是它的父函数。
-
x 轴表示抽样数,如果一个函数在 x 轴占据的宽度越宽,就表示它被抽到的次数多,即执行的时间长。注意,x 轴不代表时间,而是所有的调用栈合并后,按字母顺序排列的。