Java 诊断工具 Arthas 常见命令(超详细实战教程)(二)

简介: Java 诊断工具 Arthas 常见命令(超详细实战教程)(二)

实操案例

排查函数调用异常

通过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里面查看到具体的异常信息。

微信图片_20220908130519.png

获取到的结果展开,可以用-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时,手表会打印出结果。
  • 微信图片_20220908130528.png

当异常时姓名

观看命令支持-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

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

微信图片_20220908130827.png

输入 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'

可以查看以下信息:

微信图片_20220908130909.png

寻找顶部 N 线程

查看所有地址
thread
查看具体的线程栈

查看线程ID 2的栈:

thread 2

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

thread -n 3

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

thread -n 3 -i 5000

寻找是否有拒绝

thread -b

更多使用查看:

相关文章
|
6月前
|
人工智能 监控 Java
Java与AI智能体:构建自主决策与工具调用的智能系统
随着AI智能体技术的快速发展,构建能够自主理解任务、制定计划并执行复杂操作的智能系统已成为新的技术前沿。本文深入探讨如何在Java生态中构建具备工具调用、记忆管理和自主决策能力的AI智能体系统。我们将完整展示从智能体架构设计、工具生态系统、记忆机制到多智能体协作的全流程,为Java开发者提供构建下一代自主智能系统的完整技术方案。
821 4
|
7月前
|
人工智能 Java API
Java AI智能体实战:使用LangChain4j构建能使用工具的AI助手
随着AI技术的发展,AI智能体(Agent)能够通过使用工具来执行复杂任务,从而大幅扩展其能力边界。本文介绍如何在Java中使用LangChain4j框架构建一个能够使用外部工具的AI智能体。我们将通过一个具体示例——一个能获取天气信息和执行数学计算的AI助手,详细讲解如何定义工具、创建智能体并处理执行流程。本文包含完整的代码示例和架构说明,帮助Java开发者快速上手AI智能体的开发。
2622 8
|
7月前
|
人工智能 缓存 监控
使用LangChain4j构建Java AI智能体:让大模型学会使用工具
AI智能体是大模型技术的重要演进方向,它使模型能够主动使用工具、与环境交互,以完成复杂任务。本文详细介绍如何在Java应用中,借助LangChain4j框架构建一个具备工具使用能力的AI智能体。我们将创建一个能够进行数学计算和实时信息查询的智能体,涵盖工具定义、智能体组装、记忆管理以及Spring Boot集成等关键步骤,并展示如何通过简单的对话界面与智能体交互。
2700 1
|
10月前
|
Java 数据安全/隐私保护
银行转账虚拟生成器app,银行卡转账截图制作软件,java实现截图生成工具【仅供装逼娱乐用途】
本项目提供了一套基于Java的图片处理教学方案,包含自定义图片生成、图像水印添加及合法电子凭证生成技术示例。
|
10月前
|
Java 数据安全/隐私保护 计算机视觉
银行转账虚拟生成器app,银行卡转账截图制作软件,java实现截图生成工具【仅供装逼娱乐用途】
本内容提供Java生成自定义图片的示例代码,涵盖基础图像创建、文本添加及保存功能,适合学习2D图形编程。包括教学示例图片生成、文本图层处理和数字水印技术实现方案。
|
10月前
|
安全 Java 编译器
JD-GUI,java反编译工具及原理: JavaDecompiler一个Java反编译器
Java Decompiler (JD-GUI) 是一款由 Pavel Kouznetsov 开发的图形化 Java 反编译工具,支持 Windows、Linux 和 Mac Os。它能将 `.class` 文件反编译为 Java 源代码,支持多文件标签浏览、高亮显示,并兼容 Java 5 及以上版本。JD-GUI 支持对整个 Jar 文件进行反编译,可跳转源码,适用于多种 JDK 和编译器。其原理基于将字节码转换为抽象语法树 (AST),再通过反编译生成代码。尽管程序可能带来安全风险,但可通过代码混淆降低可读性。最新版修复了多项识别错误并优化了内存管理。
8069 1
|
11月前
|
Arthas 存储 监控
Arthas heapdump(dump java heap, 类似 jmap 命令的 heap dump 功能)
Arthas heapdump(dump java heap, 类似 jmap 命令的 heap dump 功能)
822 8
|
11月前
|
Arthas 监控 Java
Arthas vmoption(查看和修改 JVM里诊断相关的option)
Arthas vmoption(查看和修改 JVM里诊断相关的option)
219 16
|
11月前
|
机器学习/深度学习 消息中间件 存储
【高薪程序员必看】万字长文拆解Java并发编程!(9-2):并发工具-线程池
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中的强力并发工具-线程池,废话不多说让我们直接开始。
401 0
|
12月前
|
监控 Java Unix
6个Java 工具,轻松分析定位 JVM 问题 !
本文介绍了如何使用 JDK 自带工具查看和分析 JVM 的运行情况。通过编写一段测试代码(启动 10 个死循环线程,分配大量内存),结合常用工具如 `jps`、`jinfo`、`jstat`、`jstack`、`jvisualvm` 和 `jcmd` 等,详细展示了 JVM 参数配置、内存使用、线程状态及 GC 情况的监控方法。同时指出了一些常见问题,例如参数设置错误导致的内存异常,并通过实例说明了如何排查和解决。最后附上了官方文档链接,方便进一步学习。
2555 4