trace默认只统计目标方法自身耗时,不递归子调用;需显式设--depth≥2、--skipjdkmethod false才能捕获内部调用,且须用sc/sm确认真实类名与方法签名。

trace 命令为什么没打出子调用耗时
默认情况下 trace 只统计目标方法自身执行时间,不递归进入内部调用——这是最常被误解的点。它不是全链路追踪工具,而是“单层方法耗时快照”。想看到 serviceA() 里调用了 dao.select() 和 redis.get() 各花了多久,必须显式开启深度追踪。
- 加
-n 5只控制匹配次数,不影响嵌套层级 - 真正开启子调用捕获要加
--skipJDKMethod false(默认为true,会跳过所有 JDK 方法如String.valueOf、ArrayList.add) - 更关键的是加
--depth 3(默认1),数值表示最多展开几层内部调用,2能看到直接子方法,3可见孙子级 - 注意:
--depth越大,对应用性能干扰越明显,线上慎用 >2
trace 捕获不到 Spring Controller 方法的常见原因
不是方法写错了,而是类名没写对。Spring Boot 默认使用 CGLIB 动态代理,Controller 实际运行的类名是 com.example.api.UserController$$EnhancerBySpringCGLIB$$a1b2c3d4 这种带后缀的,而你 trace 的可能是原始类名。
- 先用
sc -d *UserController*查真实加载的类名,重点关注classLoaderHash和完整类名字段 - 如果看到多个匹配,用
sm com.example.api.UserController.*确认方法是否在该类中存在(排除接口定义、父类继承等干扰) - 对于 @RestController,方法签名里参数类型必须写全,比如
public Result<user> getUser(@PathVariable Long id)</user>,trace 时得写成:trace com.example.api.UserController getUser 'params[0]',不能只写getUser - 若用到了 Spring AOP 切面(如日志、事务),实际执行的是代理对象的方法,此时应 trace 代理类,而非原始类
如何用 trace 定位某次慢请求的具体调用路径
线上偶发慢请求,不能靠平均值,得抓“那一把”。Arthas 提供条件表达式能力,但语法容易写错,导致过滤失效。
- 用
watch先确认慢请求的入参特征,比如某个userId == 10086时响应超 2s,再针对性 trace:trace com.example.service.UserService loadUser '#cost > 2000 && params[0] == 10086' - 条件中
#cost是执行耗时(毫秒),只能用于trace的表达式末尾判断,不能放在中间做逻辑分支 -
params[0]是第一个参数,但若方法是loadUser(Long id, String type),且你只想看type.equals("vip")的路径,则写:'params[1].equals("vip") && #cost > 2000' - 避免字符串拼接误判:不要写
params[1] == "vip"(引用比较),要用.equals()
trace 输出结果里哪些字段真正反映瓶颈
输出看起来密密麻麻,但真正要盯住的就三个位置:最左侧缩进、cost 列、以及最后一行的 total cost。
- 缩进代表调用深度,同一层缩进里,
cost最大的那个方法最可能是瓶颈(比如jdbc.execute耗了 800ms,其他都在 10ms 内) - 注意区分
cost(本方法执行时间)和invoke cost(含子调用总耗时),前者小但后者大,说明瓶颈在子调用里 - 最后一行的
total cost是整个 trace 范围内所有节点耗时之和,若远大于目标方法的#cost,说明大量时间花在非目标方法的子调用上(比如日志框架、序列化、连接池等待) - 如果看到大量
java.lang.Thread.sleep或java.net.SocketInputStream.read,基本可判定是外部依赖(DB、Redis、HTTP)拖慢,不是代码逻辑问题
depth 控制不好、类名没对上、条件表达式写错——这三个点卡住的人最多。trace 不是黑盒扫描,它很诚实,但只反馈你明确告诉它要看的东西。










