文章目录
- 1 简介
- 2背景
- 3 图形界面工具 arthas 阿里开源
- 3.1 :启动 arthas
- 3.2 help :查看arthas所有命令
- 3.3 查看 dashboard
- 3.4 thread 列出当前进程所有线程占用CPU和内存情况
- 3.5 jvm 查看该进程的各项参数 (类比 jinfo)
- 3.6 通过 jad 来反编译 UserController Class
- 3.8 `monitor` 监控方法的执行情况
- 3.9 `watch`:检测函数返回值
- 3.10 `trace`:根据路径追踪,并记录消耗时间
- 3.11 `tt`:时间隧道,记录多个请求
- 3.12 redefine 定义class
- 3.13 退出 arthas
- 4 项目中应用
1 简介
Arthas 是一款线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率。当你遇到以下类似问题而束手无策时,Arthas 可以帮助你解决:
这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
是否有一个全局视角来查看系统的运行状况?
有什么办法可以监控到 JVM 的实时运行状态?
······
2背景
通常,本地开发环境无法访问生产环境。如果在生产环境中遇到问题,则无法使用 IDE 远程调试。更糟糕的是,在生产环境中调试是不可接受的,因为它会暂停所有线程,导致服务暂停。
开发人员可以尝试在测试环境或者预发环境中复现生产环境中的问题。但是,某些问题无法在不同的环境中轻松复现,甚至在重新启动后就消失了。
如果您正在考虑在代码中添加一些日志以帮助解决问题,您将必须经历以下阶段:测试、预发,然后生产。这种方法效率低下,更糟糕的是,该问题可能无法解决,因为一旦 JVM 重新启动,它可能无法复现,如上文所述。
Arthas 旨在解决这些问题。开发人员可以在线解决生产问题。无需 JVM 重启,无需代码更改。 Arthas 作为观察者永远不会暂停正在运行的线程。
3 图形界面工具 arthas 阿里开源
下载地址:https://arthas.aliyun.com/
简介:Arthas 是一款线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率。
3.1 :启动 arthas
直接通过java -jar 启动arthas的jar包文件
选择应用 java 进程:jvm-test 进程是第 1 个,则输入 1,再输入回车/enter
。Arthas 会 attach 到目标进程上,并输出日志:
3.2 help :查看arthas所有命令
- JVM 相关:
- 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
- class/classloader 相关:
- classloader - 查看 classloader 的继承树,urls,类加载信息,使用 classloader 去 getResource
- dump - dump 已加载类的 byte code 到特定目录
- jad - 反编译指定已加载类的源码
- mc - 内存编译器,内存编译
.java
文件为.class
文件 - redefine - 加载外部的
.class
文件,redefine 到 JVM 里 - retransform - 加载外部的
.class
文件,retransform 到 JVM 里 - sc - 查看 JVM 已加载的类信息
- sm - 查看已加载类的方法信息
- monitor/watch/trace 相关:
- monitor - 方法执行监控
- stack - 输出当前方法被调用的调用路径
- trace - 方法内部调用路径,并输出方法路径上的每个节点上耗时
- tt - 方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测
- watch - 方法执行数据观测
- 基础命令
- base64 - base64 编码转换,和 linux 里的 base64 命令类似
- cat - 打印文件内容,和 linux 里的 cat 命令类似
- cls - 清空当前屏幕区域
- echo - 打印参数,和 linux 里的 echo 命令类似
- grep - 匹配查找,和 linux 里的 grep 命令类似
- help - 查看命令帮助信息
- history - 打印命令历史
- keymap - Arthas 快捷键列表及自定义快捷键
- pwd - 返回当前的工作目录,和 linux 命令类似
- quit - 退出当前 Arthas 客户端,其他 Arthas 客户端不受影响
- reset - 重置增强类,将被 Arthas 增强过的类全部还原,Arthas 服务端关闭时会重置所有增强过的类
- session - 查看当前会话的信息
- stop - 关闭 Arthas 服务端,所有 Arthas 客户端全部退出
- tee - 复制标准输入到标准输出和指定的文件,和 linux 里的 tee 命令类似
- version - 输出当前目标 Java 进程所加载的 Arthas 版本号
3.3 查看 dashboard
输入 dashboard,按回车/enter
,会展示当前进程的信息,按ctrl+c
可以中断执行。
可以看到进程里面有哪些线程,每个线程的状态、吃CPU的情况等。
在Memory中我们可以看到内存的占用情况:
- 新生代:eden_space、survivor_space
- 老年代:tenured_space
- 非堆内存:nonheap
3.4 thread 列出当前进程所有线程占用CPU和内存情况
thread pid
会打印线程 ID pid 的栈,通常pid 1是 main 函数的线程。
thread -b 寻找死锁
3.5 jvm 查看该进程的各项参数 (类比 jinfo)
我们在garbage collectors(GC)里面可以看到这里垃圾回收的统计情况
- Copy(复制算法)用了49次,耗时506
- MarkSweepCompact(标记整理算法)用了3次,耗时795
3.6 通过 jad 来反编译 UserController Class
3.8 monitor
监控方法的执行情况
监控com.example.jvm.controller.TestController
类的 “getStr”方法 ,并且每5S更新一次状态。
monitor com.example.jvm.controller.TestController getStr -c 5
监控的维度说明
监控项 | 说明 |
---|---|
timestamp | 时间戳 |
class | Java类 |
method | 方法(构造方法、普通方法) |
total | 调用次数 |
success | 成功次数 |
fail | 失败次数 |
rt | 平均耗时 |
fail-rate | 失败率 |
3.9 watch
:检测函数返回值
方法执行数据观测,让你能方便的观察到指定方法的调用情况。
能观察到的范围为:返回值
、抛出异常
、入参
,通过编写OGNL 表达式进行对应变量的查看。
参数名称 | 参数说明 |
---|---|
class-pattern | 类名表达式匹配 |
method-pattern | 方法名表达式匹配 |
express | 观察表达式 |
condition-express | 条件表达式 |
[b] | 在方法调用之前观察before |
[e] | 在方法异常之后观察 exception |
[s] | 在方法返回之后观察 success |
[f] | 在方法结束之后(正常返回和异常返回)观察 finish |
[E] | 开启正则表达式匹配,默认为通配符匹配 |
[x:] | 指定输出结果的属性遍历深度,默认为 1 |
这里重点要说明的是观察表达式,观察表达式的构成主要由ognl 表达式组成,所以你可以这样写"{params,returnObj}",只要是一个合法的 ognl 表达式,都能被正常支持。
特别说明
- watch 命令定义了4个观察事件点,即 -b 方法调用前,-e 方法异常后,-s 方法返回后,-f 方法结束后
- 4个观察事件点 -b、-e、-s 默认关闭,-f 默认打开,当指定观察点被打开后,在相应事件点会对观察表达式进行求值并输出
- 这里要注意方法入参和方法出参的区别,有可能在中间被修改导致前后不一致,除了 -b 事件点 params 代表方法入参外,其余事件都代表方法出参
- 当使用 -b 时,由于观察事件点是在方法调用前,此时返回值或异常均不存在
通过watch命令可以查看函数的参数/返回值/异常信息。
- 查看方法执行的返回值
watch com.example.jvm.controller.UserController list returnObj
3.10 trace
:根据路径追踪,并记录消耗时间
对方法内部调用路径进行追踪,并输出方法路径上的每个节点上耗时。
简介:
trace 命令能主动搜索 class-pattern/method-pattern 对应的方法调用路径,渲染和统计整个调用链路上的所有性能开销和追踪调用链路。
观察表达式的构成主要由ognl 表达式组成,所以你可以这样写"{params,returnObj}",只要是一个合法的 ognl 表达式,都能被正常支持。
很多时候我们只想看到某个方法的rt大于某个时间之后的trace结果,现在Arthas可以按照方法执行的耗时来进行过滤了,例如trace *StringUtils isBlank '#cost>100’表示当执行时间超过100ms的时候,才会输出trace的结果。
watch/stack/trace这个三个命令都支持#cost耗时条件过滤。
参数说明:
参数名称 | 参数说明 |
---|---|
class-pattern | 类名表达匹配 |
method-pattern | 方法名表达式匹配 |
condition-express | 条件表达式,使用OGNL表达式 |
[E] | 开启正则表达式匹配,默认是通配符匹配 |
[n:] | 设置命令执行次数 |
#cost | 方法执行耗时,单位是毫秒 |
案例:
# trace函数指定类的指定方法trace com.example.jvm.controller.UserController list
# 在浏览器上进行登录操作,检查最耗时的方法trace *.DispatcherServlet *
3.11 tt
:时间隧道,记录多个请求
time-tunnel 时间隧道。
记录下指定方法每次调用的入参和返回信息,并能对这些不同时间下调用的信息进行观测
参数解析:
tt的参数 | 说明 |
---|---|
-t | 记录某个方法在一个时间段中的调用 |
-l | 显示所有已经记录的列表 |
-n 次数 | 只记录多少次 |
-s 表达式 | 搜索表达式 |
-i 索引号 | 查看指定索引号的详细调用信息 |
-p | 重新调用:指定的索引号时间碎片 |
案例:
# 最基本的使用来说,就是记录下当前方法的每次调用环境现场。tt -t com.example.jvm.controller.UserController list
模拟报错:
@Operation(summary = "业务接口模拟测试") @Parameters({ @Parameter(name = "str",description = "字符串参数",in = ParameterIn.QUERY), }) @GetMapping("work") public ResponseEntity<String> work(@RequestParam("str") String str){ if (str.equals("1")){ throw new RuntimeException("异常"); } testService.work1(); testService.work2(); testService.work3(); return ResponseEntity.ok().body("success"); } public void work1() { try { Thread.sleep(1000); }catch (InterruptedException e){ e.printStackTrace(); } log.info("work1"); } public void work2() { try { Thread.sleep(2000); }catch (InterruptedException e){ e.printStackTrace(); } log.info("work2"); } public void work3() { try { Thread.sleep(3000); }catch (InterruptedException e){ e.printStackTrace(); } log.info("work3"); }
# 对现有记录进行检索tt -l
# 需要筛选出 `primeFactors` 方法的调用信息tt -s 'method.name=="getStr"'
# 查看某条记录详细信息tt -i 1007
3.12 redefine 定义class
可以在不停止项目的情况下,修改java文件,通过javac 类名.java编译 再通过redefine 定义class上传到远程
我在Linux上放了一个小程序,输出zyw.
# 编辑T.java文件vim T.java# 编译T.java生成T.class文件javac T.java# 启动arthas 绑定TestMain进程java -jar arthas-boot.jar# 重新定义T.class 文件redefine T.class
3.13 退出 arthas
如果只是退出当前的连接,可以用quit
或者exit
命令。Attach 到目标进程上的 arthas 还会继续运行,端口会保持开放,下次连接时可以直接连接上。
如果想完全退出 arthas,可以执行stop
命令。