什么是Arthas

通常,本地开发环境无法访问生产环境,如果在生产环境中遇到问题,无法调试也无法暂停服务。那么,Arthas 作为生产环境中的观察者永远不会暂停正在运行的线程。

Arthas 是一款线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,来帮助排查线上问题。

在你的生产环境上安装Arthas

Arthas 支持在 Linux/Unix/Mac 等平台上一键安装,请复制以下内容,并粘贴到命令行中,敲 回车 执行即可:

1
curl -L https://arthas.aliyun.com/install.sh | sh

上述命令会下载启动脚本文件 as.sh 到当前目录,你可以放在任何地方或将其加入到 $PATH 中。

直接在 shell 下面执行./as.sh,就会进入交互界面。

也可以执行./as.sh -h来获取更多参数信息

或者最新版本,点击下载:[Arthas](https://arthas.aliyun.com/download/latest_version?mirror=aliyun)

直接在 shell 下面执行./as.sh,就会进入交互界面

image-20260126092510862

这里直接选择需要监控的 Java 进程就可以了,我这边服务器上就一个

image-20260126092534469

然后这样算是启动成功了,进入了 Arthas 交互界面

windows 上也可以这样安装,虽然 Arthas 是监控远程服务的,但是我也喜欢拿这个去排查本地服务存在的问题(一般是接口),也不一定就比 IDEA 调试不好用

Arthas 快速使用案例

进入到 Java 进程之后

  1. 查看整体运行状态

    1
    dashboard

    实时显示CPU、内存、GC、线程数、JVM 信息等。全局视角了解应用健康状况。

    image-20260126093232795

    解释一下其中的内容,ID / NAME就是线程 ID 和名称,STATE就是线程状态,%CPU就是CPU占用率,DAEMON意味是否守护线程(一般情况下所有业务线程都是守护线程)

    关于 Memory 内存区就是 Heap堆内存,G1 eden区、Survivor区,老年代区,然后GC了多少次,平均耗时多少,一般情况下无 Full GC,停顿时间短就正常

    非堆内存就是 Metaspace元空间,CCS区,JIT 编译代码缓存

  2. 查看线程情况

    1
    thread

    列出所有线程,按 CPU 使用率排序。一般情况下我们不用这个,用这些

    1
    2
    3
    4
    5
    6
    # 查看最忙的几个线程
    thread -n 3

    # 查看某个线程堆栈
    # thread 1会打印线程 ID 1 的栈,通常是 main 函数的线程
    thread 1
    image-20260126094028617

    挺幽默的,当前 CPU 最忙的线程是arthas自己,其实也正常,想一下,因为 thread -n 3 需要采集所有线程的 CPU 时间,这个操作本身会触发 JVM 的线程 dump,所以 Arthas 的命令执行线程会短暂“变忙”。第二忙的是 Lettuce,也就是 Spring Boot 在不设Redisson的情况下的默认客户端,然后就是 netty,这些内容都说比较清晰好读的

  3. 搜索已加载的类

    1
    2
    sc *Controller
    sc com.example.zhiyan.*Service

    这种都行,就是查看你的业务类是否被加载,也就是对应Arthas说的我改的代码有没有执行到?

    image-20260126095113588

    在这里就能看到服务已加载的类

  4. 查看某个类的方法

    1
    sm hbnu.project.zhiyanbackend.auth.service.impl.UserServiceImpl *

    列出某个类的所有方法,注意要写全限定类名,为后续 trace/watch 做准备

    image-20260126095327480
  5. 监控方法调用耗时

    它会统计该方法的调用次数、平均耗时、失败率等。

    1
    monitor hbnu.project.zhiyanbackend.projects.service.impl.ProjectServiceImpl* getAllProjects

    这里使用通配符来追踪原始类和代理类,一般情况下这样写要更简单

  6. 追踪方法的内部调用

    1
    trace hbnu.project.zhiyanbackend.projects.service.impl.ProjectServiceImpl* getAllProjects
  7. 观察方法入参和返回值

    1
    watch hbnu.project.zhiyanbackend.projects.service.impl.ProjectServiceImpl$$SpringCGLIB$$0 updateProjectStatus '{params, returnObj}' -x 3
  8. 查看 JVM 系统属性

    1
    2
    sysprop | grep port
    sysenv | grep JAVA_HOME

    这个很有用,是确认运行环境配置的,一般是确认环境变量

    image-20260126103244496
  9. 反编译类

    1
    jad hbnu.project.zhiyanbackend.projects.service.impl.ProjectServiceImpl

    直接看到 JVM 中实际加载的字节码对应的 Java 源码。确认是否部署了最新代码

    image-20260126103231171
  10. 退出

    1
    2
    3
    quit
    exit
    # 这俩都行

    Arthas agent 会自动 detach,原 Java 进程继续运行。

Arthas表达式核心变量

无论是匹配表达式也好、观察表达式也罢,他们核心判断变量都是围绕着一个 Arthas 中的通用通知对象 Advice 进行。

它的简略代码结构如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Advice {

private final ClassLoader loader; # 本次调用类所在的 ClassLoader
private final Class<?> clazz; # 本次调用类的 Class 引用
private final ArthasMethod method; # 本次调用方法反射引用
private final Object target; # 本次调用类的实例
private final Object[] params; # 本次调用参数列表
private final Object returnObj; # 本次调用返回的对象
private final Throwable throwExp; # 本次调用抛出的异常
private final boolean isBefore;
private final boolean isThrow;
private final boolean isReturn;

// getter/setter
}

Arthas 使用 OGNL(Object-Graph Navigation Language) 作为表达式语言。你可以在命令中直接使用上述字段,就像它们是局部变量一样。

1
2
3
4
5
6
7
8
9
10
11
# 观察方法参数
watch com.example.Service doSomething 'params'

# 观察返回值(仅在正常返回时)
watch com.example.Service doSomething 'returnObj' '#isReturn'

# 观察异常(仅在抛异常时)
watch com.example.Service doSomething 'throwExp' '#isThrow'

# 条件过滤:只观察第一个参数等于 "admin" 的调用
watch com.example.Service login 'params[0]' 'params[0]=="admin"'

但是 Arthas 在解析 OGNL 表达式时,只将 Advice 对象的字段暴露为上下文变量

常用的 ONGL 观察表达式如下

表达式 说明
{params} 所有参数(数组)
params[0] 第一个参数
target 当前对象(this)
target.fieldName 当前对象的某个字段
returnObj 返回值
throwExp 异常对象(仅 -e 时有效)
#cost 方法耗时(毫秒)
@ClassName@staticField 静态字段
@ClassName@staticMethod() 静态方法

命令

基础命令

cls

清屏,清空当前屏幕

终端模式下才能使用 cls 指令

history

image-20260126110532111

pwd

image-20260126110626696

返回工作目录,和Linux的一样

reset

重置增强类,将被 Arthas 增强过的类全部还原,Arthas 服务端stop时会重置所有增强过的类

一般情况下,在 trace,monitor 等这种会向方法中放入切面的类需要重置

session

查看当前会话的信息,也就是监控的是哪个进程id的java进程

image-20260126110647016

quit

退出当前 Arthas 客户端

stop

关闭 Arthas 服务端,所有 Arthas 客户端全部退出

JVM

dashboard

介绍过了,查看当前系统的实时数据面板

getstatic

查看类的静态属性,命令结构:

1
getstatic class_name field_name

heapdump

生成当前 Java 进程的堆内存快照,Heap Dump是堆转储,是一个 .hprof 文件。记录了 Java 虚拟机(JVM)在某一时刻堆内存中所有对象的状态,一般使用 JProfiler 分析

1
heapdump arthas-output/dump.hprof

生成文件在arthas-output目录,而且这个文件可以通过浏览器直接下载

1
http://localhost:8563/arthas-output/dump.hprof
image-20260126104751065

可以看到多出来了一个文件,默认是 JProfiler 分析

image-20260126104837168

当然,这样看起来太混杂了,一般情况下,我们只导出存活的对象,即从 GC Roots 可达的对象

1
heapdump --live /tmp/dump.hprof

如果不加 --live,dump 文件会包含已经被标记为垃圾但尚未回收的对象

jvm

查看当前 JVM 信息,它是相对详细的当前 JVM 情况

image-20260126105202188

参数太多了,只挑里面最重要的说了

使用的虚拟机

1
VM-NAME      Java HotSpot(TM) 64-Bit Server VM

内存使用情况

1
2
3
4
5
6
7
8
9
HEAP-MEMORY-USAGE
init : 532676608 (508.0 MiB)
used : 237772352 (226.8 MiB) # 当前使用的堆内存
committed: 310378496 (296.0 MiB)
max : 8514437120 (7.9 GiB) # 堆内存最大可达

# 非堆内存
NO-HEAP-MEMORY-USAGE
used : 222737088 (212.4 MiB)

垃圾回收器(GC)情况

1
2
3
4
GARBAGE-COLLECTORS
G1 Young Generation : count=35, time=209 ms # 年轻代 GC 发生情况
G1 Concurrent GC : count=24, time=110 ms
G1 Old Generation : count=0, time=0 ms # 老年代 GC 发生情况

线程情况

1
2
3
4
5
6
THREAD
COUNT : 57 (当前线程数)
DAEMON-COUNT : 41 (守护线程数)
PEAK-COUNT : 59 (历史峰值)
STARTED-COUNT: 94 (JVM 启动以来创建过的线程总数)
DEADLOCK-COUNT: 0 (死锁线程数)

类加载情况

1
2
3
4
CLASS-LOADING
LOADED-CLASS-COUNT : 31780 (当前加载的类数)
TOTAL-LOADED-CLASS-COUNT: 32311 (总共加载过的类数)
UNLOADED-CLASS-COUNT : 531 (卸载的类数)

启动参数

image-20260126105555741

mbean

mbean,全称Managed Bean,意思是可管理的 bean,是 Java 应用实现 可观测性 的重要机制

mbean 命令让你在不离开 Arthas 控制台的情况下,直接查看或监控任意 MBean 的属性值

1
2
3
4
5
6
7
8
# 列出所有 MBean 名称
mbean

# 查看某个 MBean 的元信息
mbean -m java.lang:type=Memory

# 查看 MBean 的属性值
mbean java.lang:type=Threading
image-20260126105911834
1
2
# 实时监控
mbean -i 1000 -n 50 java.lang:type=Threading *Count
image-20260126110017719

memory

相当于是把命令 jvm 中的内存部分给单独拿出来了,字段的解释是一样的

image-20260126112112560

sysenv

查看当前 JVM 的环境属性

1
2
# 查看所有环境变量
sysenv
image-20260126112156945
1
2
# 查看单个环境变量
sysenv JAVA_HOME
image-20260126112234495

sysprop

查看当前 JVM 的系统属性

1
2
# 查看所有属性
sysprop
image-20260126112320734
1
2
3
4
# 查看单个属性
sysprop java.version
# 而且还可以修改,可以方便的调整内存的大小
sysprop user.country CN
image-20260126112406177

thread

查看当前 JVM 的线程堆栈信息

命令 参数名称 参数说明
thread 23 id 线程 id
thread -n 3 [n:] 指定最忙的前 N 个线程并打印堆栈
thread -b [b] 找出当前阻塞其他线程的线程
thread -n 3 -i 1000 [i <value>] 指定 cpu 使用率统计的采样间隔,单位为毫秒,默认值为 200
thread [–all] 显示所有匹配的线程

这个统计也会产生一定的开销(JDK 这个接口本身开销比较大),因此会看到 as 的线程占用一定的百分比,为了降低统计自身的开销带来的影响,可以把采样间隔拉长一些,比如 5000 毫秒。

就是额外说一下 thread -b,它仅支持 synchronized 关键字造成的阻塞,不支持 ReentrantLockReadWriteLockjava.util.concurrent 锁,因为 JMX 无法直接获取其持有者

二编:还有一个比较实用的命令thread --state 状态,只显示处于某个状态的线程

可用状态值:

  • NEW
  • RUNNABLE
  • BLOCKED
  • WAITING
  • TIMED_WAITING
  • TERMINATED

class/classloader 相关

有空再说吧

观测,追踪,分析方法相关命令

请注意,这些命令,都通过字节码增强技术来实现的,会在指定类的方法中插入一些切面来实现数据统计和观测,因此在线上、预发使用时,请尽量明确需要观测的类、方法以及条件,诊断结束要执行 stop 或将增强过的类执行 reset 命令。

monitor

monitor 命令是一个非实时返回命令,需要等待目标 Java 进程返回信息,直到用户输入 Ctrl+C 为止。

monitor 命令用于对指定类和方法的调用进行周期性统计监控,每过一段时间(默认 60 秒)输出一次汇总数据,包括调用次数(total),成功次数(success),失败次数(fail),平均响应时间(avg-rt),失败率(fail-rate)

文档给出的参数是这样

参数 说明 示例
-c <秒> 统计周期(必用!),单位秒,默认 60 秒 -c 5 → 每 5 秒输出一次
"condition" 条件表达式,只统计满足条件的调用(OGNL 语法) "params[0] > 100"
-b 在方法执行前计算条件表达式(默认是在方法返回后) -b "params[0] <= 2"
-m <N>--maxMatch <N> 最大匹配类数量(防止匹配太多类影响性能) -m 1
-E 使用正则表达式匹配类/方法名(默认是通配符 * -E "com\.service\..*Service"

那么。监控某个核心接口的健康状况可以这样

1
2
# 每 10 秒监控一次全部任务查询服务
monitor -c 10 hbnu.project.zhiyanbackend.projects.service.impl.ProjectServiceImpl getProjectById
image-20260126113933660

然后,我们也可以只监控特定参数的调用,也就是条件过滤

1
2
# 只监控用户 ID = 123873434 的登录请求
monitor -c 5 com.example.UserService login "params[0] = 123873434"

这个 params 去看上面 Advice 的结构

还可以,在方法执行前就过滤,避免无效计入

1
2
# 监控“小数值”输入的情况(在方法执行前判断)
monitor -b -c 5 demo.MathGame primeFactors "params[0] <= 2"
  • 不加 -b:即使方法因参数非法快速抛异常,也会被计入统计。
  • -b:只有满足 params[0] <= 2 的调用才会被监控。

当然也可以用正则和通配符监控多个方法,官方文档给的是不要大于10个

1
monitor -c 10 hbnu.project.zhiyanbackend.projects.service.impl.ProjectServiceImpl get*
image-20260126114254753

trace

trace 命令用于追踪指定方法内部的调用路径,并输出每一层子方法的执行耗时(RT)

我说 arthas 就一个这个命令就神了,byd我无数次方法接口慢请求全是这个救下来的

它能回答:

  • 这个方法为什么慢?
  • 时间到底花在哪个子方法上了?
  • 是数据库查询慢?还是 JSON 序列化慢?还是网络调用慢?

文档上关于参数的描述

参数 说明 实战建议
-n <N> 最多捕获 N 次调用后自动退出 避免无限输出,强烈推荐使用(如 -n 3
'#cost > 10' 只 trace 耗时 >10ms 的调用 快速聚焦慢请求,最常用技巧!
--skipJDKMethod false 包含 JDK 方法(如 StringBuilder.append 当怀疑是 JDK 操作慢时开启
-E 使用正则匹配类/方法名 精确匹配多个类:trace -E com.service.A|com.service.B method
--exclude-class-pattern 排除某些类(如过滤掉日志) trace javax.servlet.Filter * --exclude-class-pattern com.demo.TestFilter
-m <N> 限制最大匹配类数量 防止通配符匹配过多类导致性能问题
-v 打印条件表达式计算详情 trace 无输出时,用它排查是“没调用”还是“条件不满足”

例如常用的。找出慢接口的瓶颈。可以使用

1
2
# 对于ProjectServiceImpl 中的get方法,只追踪耗时 >100ms 的调用,最多抓 5 次
trace -n 5 hbnu.project.zhiyanbackend.projects.service.impl.ProjectServiceImpl get* '#cost > 100'
image-20260126115040289

那么我们来分析一下输出,其中ProjectServiceImpl:getPublicActiveProjectsDTO()开始进入业务,业务部分消耗了 99.94%,convertToDTOList()findPublicActiveProjects()分别占用了75.71%23.9%,所以我们能知道。整个方法慢,主要因为 convertToDTOList() 太慢,然后就可以针对相关内容进行代码的寻思和我寻思了)),对了井号后面的是行号

别忘了 reset

image-20260126115709273

针对项目的一些底层方法,就可以使用如下命令,带着 JDK 一块分析

1
trace --skipJDKMethod false hbnu.project.zhiyanbackend.basic.utils.JsonUtils toJsonString

一般使用 trace 只能追踪一级的调用。但通过 --listenerId,你可以动态深入其中的子方法

一般在输入完命令后,在 Arthas 自己的 cost 的后面,会有一个listenerId

看到上面我 trace 的 get 方法中 listenerId 是 9,所以,我们可以再开启一个终端,连接同一 Arthas 会话,针对上面感觉慢的请求,深入子方法

1
trace -n 5 hbnu.project.zhiyanbackend.projects.service.impl.ProjectServiceImpl convertToDTOList save --listenerId 9

回到原来的终端,现在输出会包含 convertToDTOList() 内部的调用链!

这样就能按需深入,避免一次性 trace 太多方法导致性能瞬间爆炸。

stack

输出当前方法被调用的调用路径

也就是说,看这个方法到底是谁调的?用于在目标方法被调用时,打印出完整的调用栈**

如下是我对文档中的该命令提供的参数的整理

参数 作用 示例 为什么重要?
-n N 最多捕获 N 次后自动退出 stack -n 3 Service method 避免无限输出,强烈推荐必加!
'条件表达式' 只在满足条件时打印栈 stack Service delete "params[0] == 'admin'" 精准定位特定场景
'#cost > X' 只在方法耗时 >X ms 时打印 stack Service query '#cost > 100' 结合性能问题溯源
-E 使用正则匹配类/方法 stack -E com.service.* delete|remove 一次性监控多个方法
-m N 限制最大匹配类数量 stack -m 1 Service * 防止通配符匹配过多类
-v 打印条件表达式计算详情 stack -v Service method 'params[0]>10' 当无输出时排查原因

那么,就可以使用下述命令,找出谁调用了上述convertToDTOList()的操作

1
stack -n 3 hbnu.project.zhiyanbackend.projects.service.impl.ProjectServiceImpl convertToDTOList
image-20260126131855898
  • 调用顺序是从上到下的类依次的一个调用链,顺序是 类.方法:行号

这个通常和上面的 trace 命令结合使用,例如在这里就可以结合耗时分析慢调用的来源,限制时间和匹配的数量

1
2
# 只打印耗时 >100ms 的 findUserProjects 方法的调用栈
stack hbnu.project.zhiyanbackend.projects.repository.ProjectRepository findUserProjects -m 1 '#cost > 100'

当然你可与监控多个相关方法

这个也可以追踪第三方库的内部调用

1
2
# 查看谁调用了 Redis 的 set 方法
stack redis.clients.jedis.Jedis set

watch

watch 命令用于在指定方法的特定执行阶段(调用前/返回后/异常时),观测你关心的变量值(如参数、返回值、当前对象属性等)

最多的使用常见就是

  • 方法返回了错误结果,但不知道输入参数是什么
  • 想确认某个服务是否被传入了空值
  • 怀疑对象状态被意外修改,想对比调用前后的字段值,=
  • 验证缓存是否生效,观察返回值是否来自 DB

四个观察时机

参数 时机 能观测的内容 典型用途
-b Before(方法调用前) params, target 查看原始入参、对象初始状态
-s Success(正常返回后) params, target, returnObj 查看返回值、最终对象状态
-e Exception(抛出异常后) params, target, throwExp 捕获异常 + 上下文
-f Finally(无论成功/异常) 同上 默认开启,通用监控

如下是watch命令的参数

参数 作用 示例 为什么重要?
express 要观测的表达式(OGNL) '{params, returnObj}' 决定输出什么内容
condition-express 过滤条件(OGNL) 'params[0] == null' 只关注特定场景
-x N 对象展开深度(1~4) -x 3 查看嵌套对象内部字段
-n N 最多捕获 N 次 -n 3 避免无限输出,强烈推荐!
-v 打印条件表达式计算详情 -v 'params[0]>10' 当无输出时排查原因
-E 使用正则匹配类/方法 -E com.service.* save|update 精准匹配多个目标

那么,在我们调试空指针的时候,就会经常使用这样的命令

1
2
# 当第一个参数为 null 时打印
watch com.example.Service process 'params[0]' 'params[0] == null' -n 3

那么,抛出异常的时候,就可以这样编写命令捕获异常上下文,但是通常情况下,这个可以看日志,不用这样,这里只是演示

1
watch com.example.PaymentService charge '{params, throwExp}' -e -x 3

验证缓存是否命中也十分常用

1
2
# 对比调用前后的返回值(如果是 DB 查询,第二次应更快且相同)
watch com.example.repo.UserRepository findById '{returnObj}' -s -n 3

也可以结合 trace 耗时分析慢请求,看看它返回了什么导致了这种问题

1
2
# 只观察耗时 >200ms 的调用
watch com.example.Service heavyMethod '{params,returnObj, #cost}' '#cost > 200' -s -x 3

注意,watch 会植入字节码,避免长期监控高频方法,watch是影响很大的命令

tt

watch 虽然很方便和灵活,但需要提前想清楚观察表达式的拼写,这对排查问题而言要求太高,因为很多时候我们并不清楚问题出自于何方,只能靠蛛丝马迹进行猜测。

这个时候如果能记录下当时方法调用的所有入参和返回值、抛出的异常会对整个问题的思考与判断非常有帮助。

于是乎,TimeTunnel 命令就诞生了

tt(Time Tunnel)命令会自动记录指定方法每次调用的完整上下文(入参、返回值、异常、对象、耗时等),并支持后续按条件检索、查看细节,甚至“重放”该次调用!

tt 相关功能在使用完之后,需要手动释放内存,否则长时间可能导致OOM。退出 arthas 不会自动清除 tt 的缓存 map。

命令基本按照如下情况使用

1
2
3
4
5
6
7
8
# 记录 demo.MathGame.primeFactors 的所有调用
tt -t demo.MathGame primeFactors

# 限制最多记录 25 次(防 OOM)
tt -t demo.MathGame primeFactors -n 25

# 只记录参数为负数的调用
tt -t demo.MathGame primeFactors 'params[0] < 0'

列出所有记录

1
2
# 查看已记录的调用列表
tt -l

重放调用

1
2
3
4
5
# 重放 INDEX=1004 的调用(用相同参数重新执行)
tt -i 1004 -p

# 重放 3 次,间隔 500ms
tt -i 1004 -p --replay-times 3 --replay-interval 500

别忘了清理记录,因为退出 arthas 不会自动清除 tt 的缓存 map

1
2
3
4
5
# 删除单条记录
tt -d -i 1001

# 清空所有记录(防止 OOM)
tt --delete-all

输出字段大约如下

字段 说明
INDEX 唯一编号,后续操作都靠它(如 -i 1003
TIMESTAMP 调用发生的时间
COST(ms) 方法耗时(毫秒)
IS-RET 是否正常返回(true/false)
IS-EXP 是否抛出异常(true/false)
OBJECT 执行对象的 hashCode()(非内存地址!)
CLASS/METHOD 类名和方法名

我貌似就用过几次 tt,最近一次是这样的

1
2
3
4
5
6
7
# 记录所有调用,等出错后查
tt -t hbnu.project.zhiyanbackend.auth.service.impl.AuthServiceImpl login -n 20

# 出错后,查异常调用
tt -s 'isException == true'

tt -i 1005 # 查看参数和异常堆栈

profiler

profiler 命令支持生成应用热点的火焰图。本质上是通过不断的采样,然后把收集到的采样结果生成火焰图。

profiler 命令基本运行结构是 profiler action [actionArg]

这东西很有用了,对于如下情况

  • 应用 CPU 使用率突然飙升到 100%,但不知道哪个方法在疯狂执行?
  • 想知道哪些代码路径最耗时
  • 接口响应慢,但 trace 太细,需要全局视角?
  • 怀疑有内存泄漏,想看对象在哪里被大量创建?

它相当于 线上版的 JProfiler / YourKit

一般情况下这样使用

1
2
3
4
5
6
7
8
# 1. 启动采样(默认 CPU)
profiler start

# 2. 等待一段时间(或指定 -d 30 自动运行 30 秒)
# (期间应用正常运行)

# 3. 停止采样并生成火焰图
profiler stop --format flamegraph

对于官方文档的参数整理如下

参数 作用 示例 为什么重要?
-e EVENT 采样事件类型 -e cpu, -e alloc, -e lock 决定分析方向(CPU/内存/锁)
-d SECONDS 自动运行指定秒数 -d 60 避免手动 Ctrl+C,适合自动化
-f FILE 指定输出文件 -f /tmp/cpu.html 方便下载和查找
-o FORMAT 输出格式 -o flamegraph, -o jfr 火焰图 or JFR(用于高级分析)
-t 按线程分别分析 profiler start -t 查看哪个线程最忙
-j DEPTH 限制 Java 栈深度 -j 256 避免过深栈干扰分析
--include/--exclude 过滤栈帧 --include 'com.myapp.*' 聚焦业务代码,忽略框架

常见使用的场景如下

1
2
3
4
5
6
7
8
9
10
11
# 采样 60 秒,生成火焰图
profiler start -d 60 -f /tmp/cpu_ $ (date +%s).html

# 采样内存分配,只保留未回收对象(排查泄漏)
profiler start -e alloc --live -d 30 -f /tmp/alloc.html

# 记录竞争时间 >5ms 的锁
profiler start -e lock --lock 5ms -d 60 -f /tmp/lock.html

# 生成 JFR 文件,可用 JMC 或 JProfiler 分析
profiler start -e cpu -o jfr -f /tmp/profile.jfr

优先使用 -d:避免忘记停止采样

image-20260126184145964

差不多火焰图就这样

宽度表示该函数占用 CPU 的比例,越宽越热点;高度表示栈的调用深度

jfr

Java Flight Recorder (JFR) 是一种用于收集有关正在运行的 Java 应用程序的诊断和分析数据的工具。它集成到 Java 虚拟机 (JVM) 中,几乎不会造成性能开销,因此即使在负载较重的生产环境中也可以使用。

jfr 命令支持在程序动态运行过程中开启和关闭 JFR 记录。 记录收集有关 event 的数据。事件在特定时间点发生在 JVM 或 Java 应用程序中。每个事件都有一个名称、一个时间戳和一个可选的有效负载。负载是与事件相关的数据,例如 CPU 使用率、事件前后的 Java 堆大小、锁持有者的线程 ID 等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# jfr 命令基本运行结构是 
jfr cmd [actionArg]

# 启动一个新的 JFR 记录
jfr start

# 查看当前jfr记录状态
jfr status

# 导出当前记录到 .jfr 文件,不停止记录
jfr dump

# 停止记录并导出 .jfr 文件
jfr stop

整理一下官网上的常用参数如下

参数 说明 示例 为什么重要?
-n NAME 指定记录名称 -n cpu_analysis 方便识别多个记录
--duration TIME 自动停止时间 --duration 60s 避免无限记录
-f FILE 指定输出文件 -f /tmp/app.jfr 控制文件位置
-s CONFIG 指定配置文件 -s profile.jfc 控制采集粒度
--maxsize SIZE 缓冲区最大大小 --maxsize 500M 防止磁盘爆满
--maxage TIME 数据最长保留时间 --maxage 1h 自动滚动清理
--dumponexit true 退出时自动 dump --dumponexit true 确保不丢数据

最多的情况下就是快速抓取 60 秒生产数据

1
2
# 启动记录,60秒后自动停止,保存到 /tmp
jfr start -n prod_ $ (date +%s) --duration 60s -f /tmp/prod_ $ (date +%s).jfr

偶尔用到定期快照

1
2
3
4
5
# 启动长期记录(不限时)
jfr start -n long_monitor --maxsize 1G --maxage 2h

# 每隔 1 分钟手动 dump 一次(不停止)
jfr dump -r <record-id> -f /tmp/snapshot_ $ (date +%s).jfr

生成的 .jfr 文件需要用专业工具打开,使用 JProfiler 是很好的

默认情况下,arthas 使用 8563 端口,则可以打开: http://localhost:8563/arthas-output/ 查看到arthas-output目录下面的 JFR 记录结果: