⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 blog.csdn.net/qq_35427589/article/details/125331696 「共饮一杯无」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

基本概念

云杏这么多微服务,当然需要一个诊断利器来排查问题。

Arthas 是阿里开源的挚爱工具,关注开发者开发者。查问题,独立重启跟踪 Java 代码;实时 JVM 在线监控。Arthas 6+,Linux/Mac/Windows,支持 JDK 支持 Java 模式,同时提供丰富的 Tab 自动补全功能,进一步方便进行问题的定位和诊断。

官方定义为应用诊断利器,从此Javagithub获得了29.4K个star。

可以用来查看线程值,内存,GC异常和运行时,查看入参返回/显示快速应用的提示,生成图等功能,帮助排查难问题。本文主要讲述常见的使用命令。

常见命令

启动arthas-demo(案例程序)

执行如下命令下载arthas-demo.jar,再用java -jar命令启动程序:

wget https://arthas.aliyun.com/arthas-demo.jar;
java -jar arthas-demo.jar

启动arthas-boot(诊断工具程序)

执行如下命令下载arthas-boot.jar,再用java -jar命令启动:

wget https://arthas.aliyun.com/arthas-boot.jar;
java -jar arthas-boot.jar

arthas是Arthas boot的程序,用户会启动-启动所有的Java进程后,可以选择诊断需要的进程。

选择要诊断的Java程序,我这里输入1,再按回车键(回车)。

附上成功之后,会打印Arthas LOGO。

输入帮助可以获取到阿尔萨斯相关命令帮助信息。

[arthas@1266]$ help
NAME DESCRIPTION
help Display Arthas Help
auth Authenticates the current session
keymap Display all the available keymap for the specified connection.
sc Search all the classes loaded by JVM
sm Search the method of classes loaded by JVM
classloader Show classloader info
jad Decompile class
getstatic Show the static field of a class
monitor Monitor method execution statistics, e.g. total/success/failure count, average rt, fail
rate, etc.
stack Display the stack trace for the specified class and method
thread Display thread info, thread stack
trace Trace the execution time of specified method invocation.
watch Display the input/output parameter, return object, and thrown exception of specified me
thod invocation
tt Time Tunnel
jvm Display the target JVM information
memory Display jvm memory info.
perfcounter Display the perf counter information.
ognl Execute ognl expression.
mc Memory compiler, compiles java files into bytecode and class files in memory.
redefine Redefine classes. @see Instrumentation#redefineClasses(ClassDefinition...)
retransform Retransform classes. @see Instrumentation#retransformClasses(Class...)
dashboard Overview of target jvm's thread, memory, gc, vm, tomcat info.
dump Dump class byte array from JVM
heapdump Heap dump
options View and change various Arthas options
cls Clear the screen
reset Reset all the enhanced classes
version Display Arthas version
session Display current session information
sysprop Display, and change the system properties.
sysenv Display the system env.
vmoption Display, and update the vm diagnostic options.
logger Print logger info, and update the logger level
history Display command history
cat Concatenate and print files
base64 Encode and decode using Base64 representation
echo write arguments to the standard output
pwd Return working directory name
mbean Display the mbean information
grep grep command for pipes.
tee tee command for pipes.
profiler Async Profiler. https://github.com/jvm-profiling-tools/async-profiler
vmtool jvm tool
stop Stop/Shutdown Arthas server and exit the console.

与linux同样规则的命令此处不再赘述。如:history,cat,echo,pwd,grep。

系统的实时数据面板仪表板命令

仪表板可以命令查看当前系统的实时数据面板。可以查看到 CPU、内存、GC、运行环境等信息。

输入 q 或者 Ctrl+C 可以退出仪表板命令。

打印线程ID的栈线程

thread 1 命令会打印线程ID 1的栈。用thread 1 | grep 'main(' 查找到主类。

查找JVM中已加载的类 sc/sm

可以通过sc命令来查找JVM里已经加载的打印类,通过-d参数,可以出类的具体信息,很方便查找类的加载问题。

[arthas@1266]$ sc -d *MathGame
class-info demo.MathGame
code-source /home/shell/arthas-demo.jar
name demo.MathGame
isInterface false
isAnnotation false
isEnum false
isAnonymousClass false
isArray false
isLocalClass false
isMemberClass false
isPrimitive false
isSynthetic false
simple-name MathGame
modifier public
annotation
interfaces
super-class +-java.lang.Object
class-loader +-sun.misc.Launcher$AppClassLoader@1b6d3586
+-sun.misc.Launcher$ExtClassLoader@107df6e5
classLoaderHash 1b6d3586

Affect(row-cnt:1) cost in 50 ms.

sc支持通配,比如搜索所有的StringUtils:

sc *StringUtils

查找UserController的ClassLoader

[arthas@1266]$ sc -d com.example.demo.arthas.user.UserController | grep classLoaderHash
classLoaderHash 19469ea2

sm命令类似:查找类的具体函数。

sm java.math.RoundingMode

通过 -d 参数可以打印函数的具体属性:

sm -d java.math.RoundingMode

查找特定的函数,例如查找构造函数:

sm java.math.RoundingMode <init>

反编译代码 jad命令

jad demo.MathGame

通过--source-only参数可以只打印出在反编译的源代码:

jad --source-only com.example.demo.arthas.user.UserController

动态执行代码 ognl 命令

在Arthas里,有一个ognl命令,可以动态执行代码。这个有点秀啊😯😯😯

调用静态函数

ognl '@java.lang.System@out.println("hello ognl")'

获取静态类的物理场

UserController类中的logger获取字段:

ognl --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader @com.example.demo.arthas.user.UserController@logger

通过-x参数控制返回值的展开层数。

ognl --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader -x 2 @com.example.demo.arthas.user.UserController@logger

多行表达式,给与临时变量执行,返回一个列表

ognl '#value1=@System@getProperty("java.home"), #value2=@System@getProperty("java.runtime.name"), {#value1, #value2}'

  • OGNL 特殊用法请参考:https://github.com/alibaba/arthas/issues/71
  • OGNL 表达式官方指南:https://commons.apache.org/proper/commons-ognl/language-guide.html

查看函数的参数/异常信息 watch 命令/返回值

watch demo.MathGame primeFactors returnObj

查看JVM信息 sysprop sysenv jvm仪表板

系统道具

  • sysprop :打印所有的System Properties信息。
  • 指定密钥:sysprop user.dir
  • 通过grep过滤:sysprop | grep user
  • 设置新的价值:sysprop testKey testValue

系统环境

sysenv 命令可以类似地获取到环境变量。和 sysprop 命令。

虚拟机

jvm 命令会打印出 JVM 的各种详细信息。

仪表板

仪表板可以命令查看当前系统的实时数据面板。

重置类重置命令

通过reset命令可以还原类,将Arthas过码的类全部关闭,Arthas增强服务端时会重置所有过的类。Arthas在等命令的时候watch/trace,其实是应用的字节修改,插入的增强代码。显式执行重置命令,可以清除掉这些增强代码。

reset 指定类:

reset demo.MathGame

所有还原类:

reset

查看当前会话信息会话

tee 命令

传统的 tee 命令类似用于读取标准输入的数据,并将其内容输出成文件。

tee指令会从标准输入设备读取数据,将其内容输出到标准输出设备,同时保存成文件。

查看当前Arthas版本

[arthas@1710]$ version
3.6.2

退出阿尔萨斯

输入退出或退出命令可以退出阿尔萨斯当前会话。执行命令停止彻底退出阿尔萨斯。

📌PS:所有命令都可以通过 -h 参数查看帮助信息。

实操案例

排查函数调用异常

通过curl可以看到异常,但是可以看到具体的请求参数和信息。

shell@Alicloud:~$ curl http://localhost:61000/user/0
{"timestamp":1655435063042,"status":500,"error":"Internal Server Error","exception":"java.lang.IllegalArgumentException","message":"id < 1","path":"/user/0"}

查看UserController的参数/异常

在Arthas里执行:

watch com.example.demo.arthas.user.UserController * '{params, throwExp}'

  • 第一个参数是类名,支持通配
  • 第二个参数是函数名,支持通配访问curl http://localhost:61000/user/0,watch命令会打印调用的参数和异常

再次通过curl调用可以在arthas里面查看到具体的异常信息。

获取到的结果展开,可以用-x参数:

watch com.example.demo.arthas.user.UserController * '{params, throwExp}' -x 2

返回值表达式

在上面的例子里,第三个参数是返回值表达式,它实际上是一个表达式,它支持一些组合对象:

  • 装载机
  • 克拉兹
  • 方法
  • 目标
  • 参数
  • 返回对象
  • throwExp
  • 之前
  • 是抛出
  • 是返回

返回目录:

watch com.example.demo.arthas.user.UserController * '{params[0], target, returnObj}'

表达式条件

看命令支持在第4个参数里写条件表达式,比如:

  • 当访问user/1时,观察命令没有输出
  • 当访问user/101时,手表会打印出结果。

当异常时姓名

观看命令支持-e选项,表示只发射异常时的请求:

watch com.example.demo.arthas.user.UserController * "{params[0],throwExp}" -e

自动进行过滤

观看命令支持按请求进行过滤,例如:

watch com.example.demo.arthas.user.UserController * '{params, returnObj}' '#cost>200'

热更新代码

这个人也是真的秀。

访问http://localhost:61000/user/0,会返回500个异常:

shell@Alicloud:~$ curl http://localhost:61000/user/0
{"timestamp":1655436218020,"status":500,"error":"Internal Server Error","exception":"java.lang.IllegalArgumentException","message":"id < 1","path":"/user/0"}

通过热更新代码,修改这个逻辑。

jad反编译UserController

jad --source-only com.example.demo.arthas.user.UserController > /tmp/UserController.java

jad反编译的结果保存在/tmp/UserController.java文件里了。

再打开一个终端窗口,然后用vim来编辑/tmp/UserController.java

vim /tmp/UserController.java

比如说当user id小于1时,也正常返回,不抛出异常:

@GetMapping(value={"/user/{id}"})
public User findUserById(@PathVariable Integer id) {
logger.info("id: {}", (Object)id);
if (id != null && id < 1) {
return new User(id, "name" + id);
// throw new IllegalArgumentException("id < 1");
}
return new User(id.intValue(), "name" + id);
}

sc查找加载UserController的ClassLoader

[arthas@1266]$ sc -d *UserController | grep classLoaderHash
classLoaderHash 19469ea2

classLoaderHash 是 19469ea2,后面需要使用它。

麦克

保存好/tmp/UserController.java之后,使用mc(Memory Compiler)命令来编译,并通过-c或–classLoaderClass参数指定ClassLoader

mc --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader /tmp/UserController.java -d /tmp
[arthas@1266]$ mc --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader /tmp/UserController.java -d /tmp
Memory compiler output:
/tmp/com/example/demo/arthas/user/UserController.class
Affect(row-cnt:1) cost in 2879 ms.

也可以通过mc -c /tmp/UserController.java -d /tmp,使用 -c 参数指定ClassLoaderHash

mc -c 19469ea2 /tmp/UserController.java -d /tmp

重新定义

再使用redefine命令重新加载新编译好的UserController.class

[arthas@1266]$ redefine /tmp/com/example/demo/arthas/user/UserController.class
redefine success, size: 1, classes:
com.example.demo.arthas.user.UserController

热修改代码结果

重新定义成功之后,再次访问user/0,结果正常

shell@Alicloud:~$ curl http://localhost:61000/user/0
{"id":0,"name":"name0"}

动态更新应用Logger Level

查找UserController的ClassLoader

[arthas@1266]$ sc -d *UserController | grep classLoaderHash
classLoaderHash 19469ea2

用ognl获取logger

ognl --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader ‘@com.example.demo.arthas.user.UserController@logger
[arthas@1266]$ ognl --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader '@com.example.demo.arthas.user.UserController@logger'
@Logger[
serialVersionUID=@Long[5454405123156820674],
FQCN=@String[ch.qos.logback.classic.Logger],
name=@String[com.example.demo.arthas.user.UserController],
level=null,
effectiveLevelInt=@Integer[20000],
parent=@Logger[Logger[com.example.demo.arthas.user]],
childrenList=null,
aai=null,
additive=@Boolean[true],
loggerContext=@LoggerContext[ch.qos.logback.classic.LoggerContext[default]],
]

可以知道UserController@logger实际使用的是logback。可以看到level=null,则说明实际最终的level是从root logger里来的。

单独设置UserController的logger级别

ognl --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader '@com.example.demo.arthas.user.UserController@logger.setLevel(@ch.qos.logback.classic.Level@DEBUG)'

再次获取UserController@logger,可以发现已经是DEBUG了。

修改logback的记录器级别

通过获取root logger,可以修改的logger level

ognl --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader '@org.slf4j.LoggerFactory@getLogger("root").setLevel(@ch.qos.logback.classic.Level@DEBUG)'

获取Spring Context,在获取bean,再调用函数

使用tt命令获取到spring上下文

tt即TimeTunnel它可以记录下指定的方法,每次调用的入能和返回信息,并在下调用的时间对不同的进行调用。

官方tt说明:https://arthas.aliyun.com/doc/tt.html

tt -t org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter invokeHandlerMethod

访问用户/1:

curl http://localhost:61000/user/1

可以看到父母到了一个请求:

输入 q 或者 Ctrl + C 退出的 tt -t 命令。

使用tt命令从调用记录里获取到spring上下文

tt -i 1000 -w 'target.getApplicationContext()'

获取spring bean,并调用函数

tt -i 1000 -w ‘target.getApplicationContext().getBean(“helloWorldService”).getHelloMessage()’

结果如下:

[arthas@1266]$ tt -i 1000 -w 'target.getApplicationContext().getBean("helloWorldService").getHelloMessage()'
@String[Hello World]
Affect(row-cnt:1) cost in 1 ms.

排查HTTP请求返回401

请求接口没有权限的时候一般就返回401 Unauthorized。

401是被权限管理的过滤器拦截了,到底是哪个过滤器处理了这个请求,返回401?

监视所有的过滤功能

开始追踪:

trace javax.servlet.Filter *

可以在调用树的最全集,找到AdminFilterConfig$AdminFilter返回的401

+---[3.806273ms] javax.servlet.FilterChain:doFilter()
| `---[3.447472ms] com.example.demo.arthas.AdminFilterConfig$AdminFilter:doFilter()
| `---[0.17259ms] javax.servlet.http.HttpServletResponse:sendError()

通过栈调用栈

上面是通过trace命令来获取信息,从里,我们可以知道通过stack跟踪HttpServletResponse:sendError()结果,同样可以知道返回的是哪个Filter了401

执行:

stack javax.servlet.http.HttpServletResponse sendError 'params[0]==401'

可以查看以下信息:

寻找顶部 N 线程

查看所有地址

thread

查看具体的线程栈

查看线程ID 2的栈:

thread 2

查看CPU使用率最高的线程的栈

thread -n 3

查看5秒内的CPU使用率顶部线程栈

thread -n 3 -i 5000

寻找是否有拒绝

thread -b

更多使用查看:

  • Github地址:https://github.com/alibaba/arthas
  • 文档地址:https://arthas.aliyun.com/doc/
文章目录
  1. 1. 基本概念
  • 常见命令
    1. 启动arthas-demo(案例程序)
    2. 启动arthas-boot(诊断工具程序)
    3. 系统的实时数据面板仪表板命令
    4. 打印线程ID的栈线程
    5. 查找JVM中已加载的类 sc/sm
    6. 反编译代码 jad命令
    7. 动态执行代码 ognl 命令
      1. 0.1. 调用静态函数
      2. 0.2. 获取静态类的物理场
      3. 0.3. 多行表达式,给与临时变量执行,返回一个列表
  • 查看函数的参数/异常信息 watch 命令/返回值
  • 查看JVM信息 sysprop sysenv jvm仪表板
    1. 0.1. 系统道具
    2. 0.2. 系统环境
    3. 0.3. 虚拟机
    4. 0.4. 仪表板
  • 重置类重置命令
  • 查看当前会话信息会话
    1. 0.1. tee 命令
  • 查看当前Arthas版本
  • 退出阿尔萨斯
  • 实操案例
    1. 排查函数调用异常
    2. 查看UserController的参数/异常
    3. 返回值表达式
    4. 表达式条件
    5. 当异常时姓名
    6. 自动进行过滤
    7. 热更新代码
    8. jad反编译UserController
    9. sc查找加载UserController的ClassLoader
    10. 麦克
    11. 重新定义
    12. 热修改代码结果
    13. 动态更新应用Logger Level
      1. 0.1. 查找UserController的ClassLoader
  • 用ognl获取logger
  • 单独设置UserController的logger级别
  • 修改logback的记录器级别
  • 使用tt命令获取到spring上下文
  • 使用tt命令从调用记录里获取到spring上下文
  • 获取spring bean,并调用函数
  • 排查HTTP请求返回401
  • 监视所有的过滤功能
  • 通过栈调用栈
  • 寻找顶部 N 线程
    1. 0.1. 查看所有地址
    2. 0.2. 查看具体的线程栈
  • 查看CPU使用率最高的线程的栈
  • 查看5秒内的CPU使用率顶部线程栈
  • 寻找是否有拒绝