一.什么是Arthas
1.为什么需要Arthas
通常,本地开发环境无法访问生产环境。如果在生产环境中遇到问题,则无法使用 IDE 远程调试。更糟糕的是,在生产环境中调试是不可接受的,因为它会暂停所有线程,导致服务暂停。
开发人员可以尝试在测试环境或者预发环境中复现生产环境中的问题。但是,某些问题无法在不同的环境中轻松复现,甚至在重新启动后就消失了。
如果您正在考虑在代码中添加一些日志以帮助解决问题,您将必须经历以下阶段:测试、预发,然后生产。这种方法效率低下,更糟糕的是,该问题可能无法解决,因为一旦 JVM 重新启动,它可能无法复现,如上文所述。
Arthas 旨在解决这些问题。开发人员可以在线解决生产问题。无需 JVM 重启,无需代码更改。 Arthas 作为观察者永远不会暂停正在运行的线程。
2.认识Arthas
Arthas 是一款线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率。
Arthas 是 Alibaba 开源的 Java 诊断工具,深受开发者喜爱。当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决:
- 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
- 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
- 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
- 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
- 是否有一个全局视角来查看系统的运行状况?
- 有什么办法可以监控到 JVM 的实时运行状态?
- 怎么快速定位应用的热点,生成火焰图?
- 怎样直接从 JVM 内查找某个类的实例?
Arthas 支持 JDK 6+,支持 Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 Tab 自动补全功能,进一步方便进行问题的定位和诊断。
二.Arthas的使用
我们根据官方案例来进行入门:https://arthas.gitee.io/doc/quick-start.html
1.安装Arthas
启动 math-game ,math-game是一个简单的程序,每隔一秒生成一个随机数,再执行质因数分解,并打印出分解结果。
curl -O https://arthas.aliyun.com/math-game.jar
java -jar math-game.jar
启动 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$
2.查看dashboard
输入dashboard,按回车/enter,会展示当前进程的信息,按ctrl+c可以中断执行。
$ dashboard
效果如下,三个区域分别是:线程情况 ,内存情况,运行环境
这里可以看到线程情况和JVM内存情况
- id :线程ID
- name : 线程名
- state : 线程状态
- CPU% : 线程使用CPU的百分比
- heap : 使用情况
- ps_eden_space :伊甸园内存情况
- ps_survivor_space : 幸存区内存情况
- ps_old_gen : 老年区内存情况
当我们发现某个线程的CPU占用不正常的时候,可以通过 thread来获取进程的 Class情况
3.thread查看进程
通过 thread 命令来获取到math-game进程的 Main Class ,thread 线程ID
会打印线程ID的栈,通常是 main 函数的线程。
$ thread 1 | grep 'main('at demo.MathGame.main(MathGame.java:17)
4.通过 jad 来反编译
然后我们可以通过 jad 类全名
来反编译代码,进行问题排查
$ jad demo.MathGameClassLoader:
+-sun.misc.Launcher$AppClassLoader@3d4eac69+-sun.misc.Launcher$ExtClassLoader@66350f69Location:
/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);}...省略...
5.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],
]
6.trace查看线程调用耗时
在Java程序中,如果针对单个接口,我们是可以采用trace命令去查看接口的调用连耗时情况的,trace 命令能主动搜索 class-pattern/method-pattern 对应的方法调用路径,渲染和统计整个调用链路上的所有性能开销和追踪调用链路。
$ 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]
7.火焰图
当系统出现性能瓶颈,我们可以通过profiler 火焰图来分析定位问题的位置
,该命令支持生成应用热点的火焰图。本质上是通过不断的采样,然后把收集到的采样结果生成火焰图。
profiler start 开始记录火焰图
profiler getSamples 当前采样数
profiler status 当前采样时长
profiler stop 结束记录火焰图
stop之后会生成一个html文件,使用浏览器打开就可以看到火焰图情况了。http://localhost:3658/arthas-output/ 或直接打开源文件查看
这里做一下解释
- 火焰图是基于 perf 结果产生的SVG 图片,用来展示 CPU 的调用栈。
- y 轴表示调用栈,每一层都是一个函数。调用栈越深,火焰就越高,顶部就是正在执行的函数,下方都是它的父函数。
- x 轴表示抽样数,如果一个函数在 x 轴占据的宽度越宽,就表示它被抽到的次数多,即执行的时间长。注意,x 轴不代表时间,而是所有的调用栈合并后,按字母顺序排列的。
- 绿色:Java代码 ;黄色:JVM,C++代码 ;红色:用户态,C代码;橙色:内核态,C代码
- 火焰图就是看顶层的哪个函数占据的宽度最大。只要有"平顶"(plateaus),就表示该函数可能存在性能问题。
鼠标可以点击的选中的每个框就代表了一个栈里的函数,其宽度可以直接理解为CPU时间占比(其实是采样的数量以及与采样总量的占比)。那么,也就是说占比比较宽的框就表示:
- a.该函数运行时间较长(单次时间长)
- b.被调用次数较多.(调用频率高)
进而被采样的次数比较多,占用的CPU时间多。
8.更多功能
- arthas还提供了更多的功能,比如:
- dashboard - 当前系统的实时数据面板
- getstatic - 查看类的静态属性
- heapdump - dump java heap, 类似 jmap 命令的 heap dump 功能
- jvm - 查看当前 JVM 的信息
- logger - 查看和修改 logger
- mbean - 查看 Mbean 的信息
- memory - 查看 JVM 的内存信息
- ognl - 执行 ognl 表达式
- perfcounter - 查看当前 JVM 的 Perf Counter 信息
- sysenv - 查看 JVM 的环境变量
- sysprop - 查看和修改 JVM 的系统属性
- thread - 查看当前 JVM 的线程堆栈信息
- vmoption - 查看和修改 JVM 里诊断相关的 option
- vmtool - 从 jvm 里查询对象,执行 forceGc
具体看命令列表:https://arthas.gitee.io/doc/commands.html