一起来学学Arthas这个JVM监控分析神器吧!
本文从Arthas的基础使用深入到实战演练,希望能帮助广大Java Boy掌握这一监控分析利器。
一、前言
1.1 简介
先引用一下官网的原话给大家介绍一下什么是Arthas(PS:读作阿尔萨斯)
Arthas 是一款线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率。
1.2背景
通常,本地开发环境无法访问生产环境。如果在生产环境中遇到问题,则无法使用 IDE 远程调试。更糟糕的是,在生产环境中调试是不可接受的,因为它会暂停所有线程,导致服务暂停。
针对现状进行分析,我们可以发现,当前的问题排查存在以下几个痛点。
-
对于一些异常问题来说,比如常见的NullPointerException、等,我们可以通过异常堆栈来查找相关的问题,但是,对于一些并没有出现异常的问题,往往不是那么好排查
-
开发人员可以尝试在测试环境或者预发环境中复现生产环境中的问题。但是,某些问题无法在不同的环境中轻松复现,甚至在重新启动后就消失了
-
一种常见的解决方式是:在代码中添加一些日志,通过日志来判断业务的运行情况,数据的流转情况等,从而判断该问题的原因,但是该方法必须经历
- 切换hotfix分支
- 添加相应的日志代码
- Merge代码到Master
- 部署线上环境
这种方法效率低下,并且如果日志记录的不是很完善的话,又需要打上新的日志来判断问题,并且,大量的日志也会给系统带来没必要的负担
-
另外存在一种更少见的情况,那就是该问题是由于JVM内存/GC引起的,比如GC后本地缓存失效。对于此种问题,一旦JVM重新启动,它可能在短时间内无法复现
Arthas 旨在解决以上这些问题。开发人员可以在线解决生产问题。无需 JVM 重启,无需代码更改。 Arthas 作为观察者永远不会暂停正在运行的线程。
1.3Arthas能为你做什么
Arthas
是 Alibaba 开源的 Java 诊断工具,深受开发者喜爱。
当你遇到以下类似问题而束手无策时,Arthas
可以帮助你解决:
- 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
- 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
- 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
- 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
- 是否有一个全局视角来查看系统的运行状况?
- 有什么办法可以监控到 JVM 的实时运行状态?
- 怎么快速定位应用的热点,生成火焰图?
- 怎样直接从 JVM 内查找某个类的实例?
Arthas
支持 JDK 6+,支持 Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 Tab
自动补全功能,进一步方便进行问题的定位和诊断。
二、使用
在教大家如何使用前,更应该教大家如何学习Arthas的使用。
由于Arthas使用场景往往是线上问题的监控与分析,需要我们快准狠的解决问题。Arthas的命令众多,但是由于有Arthas-Idea插件,以及官方文档和搜索引擎,在这里,我并不推荐大家把命令死记硬背下来,我们应该搞清楚Arthas它能够帮我做什么事情,如1.3提到的那些功能,我们应该对它具备的功能十分熟悉,并且,能够知道该功能对应的命令是啥(这里记不住的话也行,可以查资料,但是一定要熟悉它有哪些功能,不然在真正的应用中,你将无法想到可以使用Arthash)。
Arthas-Idea插件安装
实际使用中,如果自己手动输命令,超长的类名绝对是噩梦,就算是复制粘贴也是很麻烦的,比如用的很多的watch,trace这种。不过还好,现在有插件能帮我们摆脱这个痛苦。
安装完插件之后,右键点击类或者方法,点击Arthas Command,我们可以发现右边出现了很多选项,这些选项对应着Arthas里面的功能,我们只需要单击其中一个,就可以复制相关的命令到剪贴板,然后去Arthas控制台使用即可,如果想修改命令的参数(常见的比如-n:监控最大执行次数,-x:返回嵌套的最大深度),我们可以自己手动修改然后再执行。
这真是一个伟大的插件!
快速入门
下面的内容摘自官方文档:Arthas文档
1. 启动 math-game
curl -O https://arthas.aliyun.com/math-game.jar
java -jar math-game.jar
math-game
是一个简单的程序,每隔一秒生成一个随机数,再执行质因数分解,并打印出分解结果。
math-game
源代码:查看在新窗口打开
2. 启动 arthas
在命令行下面执行(使用和目标进程一致的用户启动,否则可能 attach 失败):
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar
- 执行该程序的用户需要和目标进程具有相同的权限。比如以
admin
用户来执行:sudo su admin && java -jar arthas-boot.jar
或sudo -u admin -EH java -jar arthas-boot.jar
。 - 如果 attach 不上目标进程,可以查看
~/logs/arthas/
目录下的日志。 - 如果下载速度比较慢,可以使用 aliyun 的镜像:
java -jar arthas-boot.jar --repo-mirror aliyun --use-http
java -jar arthas-boot.jar -h
打印更多参数信息。
选择应用 java 进程:
$ $ java -jar arthas-boot.jar
* [1]: 35542
[2]: 71560 math-game.jar
math-game
进程是第 2 个,则输入 2,再输入回车/enter
。Arthas 会 attach 到目标进程上,并输出日志:
[INFO] Try to attach process 71560
[INFO] Attach process 71560 success.
[INFO] arthas-client connect 127.0.0.1 3658
,---. ,------. ,--------.,--. ,--. ,---. ,---.
/ O \ | .--. ''--. .--'| '--' | / O \ ' .-'
| .-. || '--'.' | | | .--. || .-. |`. `-.
| | | || |\ \ | | | | | || | | |.-' |
`--' `--'`--' '--' `--' `--' `--'`--' `--'`-----'
wiki: https://arthas.aliyun.com/doc
version: 3.0.5.20181127201536
pid: 71560
time: 2018-11-28 19:16:24
$
3. 查看 dashboard
输入dashboard,按回车/enter
,会展示当前进程的信息,按ctrl+c
可以中断执行。
$ dashboard
ID NAME GROUP PRIORI STATE %CPU TIME INTERRU DAEMON
17 pool-2-thread-1 system 5 WAITIN 67 0:0 false false
27 Timer-for-arthas-dashb system 10 RUNNAB 32 0:0 false true
11 AsyncAppender-Worker-a system 9 WAITIN 0 0:0 false true
9 Attach Listener system 9 RUNNAB 0 0:0 false true
3 Finalizer system 8 WAITIN 0 0:0 false true
2 Reference Handler system 10 WAITIN 0 0:0 false true
4 Signal Dispatcher system 9 RUNNAB 0 0:0 false true
26 as-command-execute-dae system 10 TIMED_ 0 0:0 false true
13 job-timeout system 9 TIMED_ 0 0:0 false true
1 main main 5 TIMED_ 0 0:0 false false
14 nioEventLoopGroup-2-1 system 10 RUNNAB 0 0:0 false false
18 nioEventLoopGroup-2-2 system 10 RUNNAB 0 0:0 false false
23 nioEventLoopGroup-2-3 system 10 RUNNAB 0 0:0 false false
15 nioEventLoopGroup-3-1 system 10 RUNNAB 0 0:0 false false
Memory used total max usage GC
heap 32M 155M 1820M 1.77% gc.ps_scavenge.count 4
ps_eden_space 14M 65M 672M 2.21% gc.ps_scavenge.time(m 166
ps_survivor_space 4M 5M 5M s)
ps_old_gen 12M 85M 1365M 0.91% gc.ps_marksweep.count 0
nonheap 20M 23M -1 gc.ps_marksweep.time( 0
code_cache 3M 5M 240M 1.32% ms)
Runtime
os.name Mac OS X
os.version 10.13.4
java.version 1.8.0_162
java.home /Library/Java/JavaVir
tualMachines/jdk1.8.0
_162.jdk/Contents/Hom
e/jre
4. 通过 thread 命令来获取到math-game
进程的 Main Class
thread 1
会打印线程 ID 1 的栈,通常是 main 函数的线程。
参数名称 | 参数说明 |
---|---|
id | 线程 id |
[n:] | 指定最忙的前 N 个线程并打印堆栈 |
[b] | 找出当前阻塞其他线程的线程 |
[i <value> ] | 指定 cpu 使用率统计的采样间隔,单位为毫秒,默认值为 200 |
[--all] | 显示所有匹配的线程 |
$ thread 1 | grep 'main('
at demo.MathGame.main(MathGame.java:17)
5. 通过 jad 来反编译 Main Class
$ jad demo.MathGame
ClassLoader:
+-sun.misc.Launcher$AppClassLoader@3d4eac69
+-sun.misc.Launcher$ExtClassLoader@66350f69
Location:
/tmp/math-game.jar
/*
* Decompiled with CFR 0_132.
*/
package demo;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Iterator;
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();
do {
game.run();
TimeUnit.SECONDS.sleep(1L);
} while (true);
}
public void run() throws InterruptedException {
try {
int number = random.nextInt();
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 + "=");
Iterator<Integer> iterator = primeFactors.iterator();
while (iterator.hasNext()) {
int factor = iterator.next();
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;
}
}
Affect(row-cnt:1) cost in 970 ms.
6. watch
通过watch命令来查看demo.MathGame#primeFactors
函数的返回值:
$ watch demo.MathGame primeFactors returnObj
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 107 ms.
ts=2018-11-28 19:22:30; [cost=1.715367ms] result=null
ts=2018-11-28 19:22:31; [cost=0.185203ms] result=null
ts=2018-11-28 19:22:32; [cost=19.012416ms] result=@ArrayList[
@Integer[5],
@Integer[47],
@Integer[2675531],
]
ts=2018-11-28 19:22:33; [cost=0.311395ms] result=@ArrayList[
@Integer[2],
@Integer[5],
@Integer[317],
@Integer[503],
@Integer[887],
]
ts=2018-11-28 19:22:34; [cost=10.136007ms] result=@ArrayList[
@Integer[2],
@Integer[2],
@Integer[3],
@Integer[3],
@Integer[31],
@Integer[717593],
]
ts=2018-11-28 19:22:35; [cost=29.969732ms] result=@ArrayList[
@Integer[5],
@Integer[29],
@Integer[7651739],
]
更多的功能可以查看进阶使用。
7. 退出 arthas
如果只是退出当前的连接,可以用quit
或者exit
命令。Attach 到目标进程上的 arthas 还会继续运行,端口会保持开放,下次连接时可以直接连接上。
如果想完全退出 arthas,可以执行stop
命令。
TIPS:由于Arthas命令众多,这里就不在详细的介绍,大家可以自行去官方文档和官方教程中学习。
除此之外,再给大家推荐官方的Arthas教程。
三、实践
下面针对我个人对于Arthas常见的应用场景,给大家带来了一些命令的实际场景。
3.1、占用CPU最高的n个线程是哪些?
有时候我们会发现机器cpu飙升,这时我们最想要的就是看,到底是那些线程在做耗CPU的事情,把这些线程的堆栈打印出来,进而可以找到执行的代码,这时候thread -n就是一个不错选择。
如下使用thread -n 3 显示进程中耗CPU最高的三个线程,按照CPU消耗量递减排序显示。
$ thread -n 3
"C1 CompilerThread0" [Internal] cpuUsage=1.63% deltaTime=3ms time=1170ms
"arthas-command-execute" Id=23 cpuUsage=0.11% deltaTime=0ms time=401ms RUNNABLE
at java.management@11.0.7/sun.management.ThreadImpl.dumpThreads0(Native Method)
at java.management@11.0.7/sun.management.ThreadImpl.getThreadInfo(ThreadImpl.java:466)
at com.taobao.arthas.core.command.monitor200.ThreadCommand.processTopBusyThreads(ThreadCommand.java:199)
at com.taobao.arthas.core.command.monitor200.ThreadCommand.process(ThreadCommand.java:122)
at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl.process(AnnotatedCommandImpl.java:82)
at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl.access$100(AnnotatedCommandImpl.java:18)
at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl$ProcessHandler.handle(AnnotatedCommandImpl.java:111)
at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl$ProcessHandler.handle(AnnotatedCommandImpl.java:108)
at com.taobao.arthas.core.shell.system.impl.ProcessImpl$CommandProcessTask.run(ProcessImpl.java:385)
at java.base@11.0.7/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
at java.base@11.0.7/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base@11.0.7/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
at java.base@11.0.7/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base@11.0.7/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base@11.0.7/java.lang.Thread.run(Thread.java:834)
"VM Periodic Task Thread" [Internal] cpuUsage=0.07% deltaTime=0ms time=584ms
通过分析线程堆栈,我们可以解决CPU消耗过高的问题。
读者们可以根据自己的喜好来选择相应的工具。
3.2、哪个线程阻塞了其他线程?
有时候我们发现应用卡住了, 这可能是因为发生了死锁或者其他锁等待问题。 为了排查这类问题, arthas提供了thread -b
, 一键找出那个罪魁祸首。
如下使用thread -b 显示进程中名称为biz2的线程在获取biz1线程持有的对象时被阻塞。
[arthas@36585]$ thread -b
"biz2" Id=12 BLOCKED on java.lang.Object@ba7da9c owned by "biz1" Id=11
at org.JDk8Stream.App$2.run(App.java:81)
- blocked on java.lang.Object@ba7da9c
- locked java.lang.Object@549c3b6a <---- but blocks 1 other threads!
at java.lang.Thread.run(Thread.java:748)
通过分析线程堆栈,我们可以知道是at org.JDk8Stream.App$2.run(App.java:81)
获取的锁对象
注意, 目前只支持找出 synchronized 关键字阻塞住的线程, 如果是java.util.concurrent.Lock
, 目前还不支持。
如果是Lock锁,我们可以使用jstack命令来排查死锁。同样,jstack命令也能用来排查synchronized锁,
如果发生了死锁,jstack命令就会展示以下信息:我们也可以定位到问题。
Found one Java-level deadlock:
=============================
"mythread2":
waiting for ownable synchronizer 0x000000076aea4848, (a java.util.concurrent.locks.ReentrantLock$NonfairSync),
which is held by "mythread1"
"mythread1":
waiting for ownable synchronizer 0x000000076aea4878, (a java.util.concurrent.locks.ReentrantLock$NonfairSync),
which is held by "mythread2"
Java stack information for the threads listed above:
===================================================
"mythread2":
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000076aea4848> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:209)
at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
at com.hua.jvm.DeathLockDemo.lambda$deathLock$1(DeathLockDemo.java:39)
at com.hua.jvm.DeathLockDemo$$Lambda$2/240650537.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
"mythread1":
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000076aea4878> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:209)
at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
at com.hua.jvm.DeathLockDemo.lambda$deathLock$0(DeathLockDemo.java:29)
at com.hua.jvm.DeathLockDemo$$Lambda$1/1809787067.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.
3.3、我修改的代码在线上生效了?
这是我用的最多的一个功能,测试以及调试bug的时候,总是会不记得测试环境部署的代码是不是最新的。又或者有时候我们修改了代码,发布到了线上,但是结果可能不符合预期或者没法验证是否符合预期(比如非热点代码),那么我们可能会想,我的代码到底有没有被JVM加载生效了?
Artahs提供了,jad 命令 可以帮助我们讲 JVM 中实际运行的 class 的 byte code 反编译成 java 代码,便于你理解业务逻辑
$ jad --source-only demo.MathGame
/*
* Decompiled with CFR 0_132.
*/
package demo;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Iterator;
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;
...
3.4、观察一个方法的入参和返回值
当线上出现事故时候,有时候我们想看相关业务某个方法执行的入参和出参数据,从而确定方法逻辑执行的正确性,或者入参的合法性,而恰好没打日志,这时候怎么办呢?
Arthas提供了watch命令,用于动态观察方法执行数据,如果我们的入参和出参比较复杂,那我们可以通过-x
参数指定输出结果的属性遍历深度,如下所示
$ watch demo.MathGame primeFactors -x 2
Press Q or Ctrl+C to abort.
Affect(class count: 1 , method count: 1) cost in 32 ms, listenerId: 5
method=demo.MathGame.primeFactors location=AtExceptionExit
ts=2021-08-31 15:22:57; [cost=0.220625ms] result=@ArrayList[
@Object[][
@Integer[-179173],
],
@MathGame[
random=@Random[java.util.Random@31cefde0],
illegalArgumentCount=@Integer[44],
],
null,
]
method=demo.MathGame.primeFactors location=AtExit
ts=2021-08-31 15:22:58; [cost=1.020982ms] result=@ArrayList[
@Object[][
@Integer[1],
],
@MathGame[
random=@Random[java.util.Random@31cefde0],
illegalArgumentCount=@Integer[44],
],
@ArrayList[
@Integer[2],
@Integer[2],
@Integer[26947],
],
]
-
我们可以发现Result数组里有三个元素,这是观察表达式的值,默认值是
{params, target, returnObj}
,该表达式可以通过参数配置- params:入参
- target:this对象
- returnObj:出参
-
上面的结果里,说明函数被执行了两次,第一次结果是
location=AtExceptionExit
,说明函数抛出异常了,因此returnObj
是 null -
在第二次结果里是
location=AtExit
,说明函数正常返回,因此可以看到returnObj
结果是一个 ArrayList
以后遇到线上问题时,不要再傻乎乎的打日志然后重新部署了,试试watch命令把。
3.5、某个方法调用链路是什么和执行耗时是多少?
有时候我们发现某个接口的rt很长,并且接口内有好多逻辑,但是我们并没有给每个逻辑都加时间监控埋点,这时候我们如何去查找到耗时比较多的逻辑那?
Artahs则提供了trace命令来帮助我们解决这个问题,其可以洞察方法内部调用路径,并输出方法路径上的每个节点上耗时,例如下面
$ trace demo.MathGame run
Press Q or Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 28 ms.
`---ts=2019-12-04 00:45:08;thread_name=main;id=1;is_daemon=false;priority=5;TCCL=sun.misc.Launcher$AppClassLoader@3d4eac69
`---[0.617465ms] demo.MathGame:run()
`---[0.078946ms] demo.MathGame:primeFactors() #24 [throws Exception]
`---ts=2019-12-04 00:45:09;thread_name=main;id=1;is_daemon=false;priority=5;TCCL=sun.misc.Launcher$AppClassLoader@3d4eac69
`---[1.276874ms] demo.MathGame:run()
`---[0.03752ms] demo.MathGame:primeFactors() #24 [throws Exception]
以后领导叫你优化某个接口,试试trace命令把,比打一堆没意义的监控日志方便多了。
3.6、方法执行情况监控
并不是所有方法都会有监控的,大多数情况HTTP接口和RPC接口都有执行情况监控,但是程序内部的方法往往是没有监控,比如本地缓存,如果我们这时候想看一下本地缓存的调用情况,应该怎么办呢?
当我们想看一下线上环境某个方法的执行情况,我们就可以使用monitor命令来查看
$ monitor -c 5 demo.MathGame primeFactors
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 94 ms.
timestamp class method total success fail avg-rt(ms) fail-rate
-----------------------------------------------------------------------------------------------
2018-12-03 19:06:38 demo.MathGame primeFactors 5 1 4 1.15 80.00%
timestamp class method total success fail avg-rt(ms) fail-rate
-----------------------------------------------------------------------------------------------
2018-12-03 19:06:43 demo.MathGame primeFactors 5 3 2 42.29 40.00%
timestamp class method total success fail avg-rt(ms) fail-rate
-----------------------------------------------------------------------------------------------
2018-12-03 19:06:48 demo.MathGame primeFactors 5 3 2 67.92 40.00%
timestamp class method total success fail avg-rt(ms) fail-rate
-----------------------------------------------------------------------------------------------
2018-12-03 19:06:53 demo.MathGame primeFactors 5 2 3 0.25 60.00%
timestamp class method total success fail avg-rt(ms) fail-rate
-----------------------------------------------------------------------------------------------
2018-12-03 19:06:58 demo.MathGame primeFactors 1 1 0 0.45 0.00%
timestamp class method total success fail avg-rt(ms) fail-rate
-----------------------------------------------------------------------------------------------
2018-12-03 19:07:03 demo.MathGame primeFactors 2 2 0 3182.72 0.00%
我们可以看到该时间段内总的调用次数、成功的次数、失败的次数以及平均执行时间和失败率等指标,对于某个接口的性能和吞吐量来说具有不错的参考价值。
3.7、生成火焰图
火焰图是很常用的一个问题分析工具,能够高度可视化的反映出系统的执行细节以及瓶颈。Arthas提供的profiler命令可以快速生成火焰图。
-
执行 profiler start,开始采样。默认进行CPU采样,可通过参数**--event**指定采样事件,如cpu、alloc、lock、wall
$ profiler start Started [cpu] profiling
-
持续采样ing...,可通过profiler status查看当前采样状态
$ profiler status [cpu] profiling is running for 4 seconds
-
获取已采样的Sample数量
$ profiler getSamples 23
-
执行profiler stop,停止采样。默认情况下,结果文件是
html
格式,也可以用--format
参数指定:$ profiler stop --format html profiler output file: /tmp/test/arthas-output/20211207-111550.html OK
或者在
--file
参数里用文件名指名格式。比如--file /tmp/result.html
。
四、总结
Java的监控、性能分析工具众多,无论是平时使用,还是面试时候跟面试官吹逼,不要只了解那几个JDK基础命令了,高级的工具咱们也学习来用起来。
转载自:https://juejin.cn/post/7231753231889612855