原创

线上故障排查(一):Arthas工具实战

温馨提示:
本文最后更新于 2026年03月08日,已超过 21 天没有更新。若文章内的图片失效(无法正常加载),请留言反馈或直接联系我

 

一、为什么需要 Arthas

在 Spring Boot 项目的线上运维中,我们常面临以下至暗时刻

  • 接口莫名变慢:用户投诉响应延迟,但日志里看不出瓶颈在哪。
  • CPU/内存飙升:服务器负载告警,却找不到是哪个线程或哪段代码在“作妖”。
  • 日志缺失:代码执行异常,但受限于日志级别或未埋点,查不到关键入参和返回值。
  • 代码不一致:怀疑线上运行的代码与 Git 仓库不一致,却无法验证。

传统排查方式的痛点

  • 重启服务:为了加日志或换配置而重启,直接影响业务可用性。
  • 效率低下:导出 Heap Dump 或 Thread Dump 文件体积巨大,下载分析耗时极长,往往错失最佳排查时机。
  • 盲目猜测:缺乏实时数据支撑,只能靠猜。

阿里开源的Arthas(阿尔萨斯)正是为解决这些痛点而生。它是一款强大的 Java 应用诊断工具,核心优势在于:

  • 零侵入:无需修改代码无需重启服务,随时 attach 到运行中的进程。
  • 全视角:从 JVM 底层(内存、GC、线程)到业务上层(方法链路、参数、返回值)全覆盖。
  • 动态化:支持热更新代码(retransform)、动态执行表达式(ognl),甚至能“时空回放”(tt)。
  • 轻量化:秒级启动,按需诊断,用完即走。

定位:它是每一位 Java 开发和运维工程师必须掌握的线上急救神器

官网文档地址:https://arthas.aliyun.com/doc/

二、快速启动

Arthas 的设计哲学是“开箱即用”。无需复杂配置,只需 3 个步骤 即可连接到你的 Spring Boot 应用:

1.下载启动包

从 Arthas 官方 GitHub Release 页面下载最新稳定版的 arthas-boot.jar

提示:建议保留最新版本,以获取最新的 Bug 修复和功能特性。

2.上传至服务器

将下载的 arthas-boot.jar 上传至目标服务器(如果是本地开发环境,直接在当前目录即可)。

3.执行命令并选择进程

在终端执行启动命令:

java -jar arthas-boot.jar

交互流程

  1. 自动扫描:命令执行后,Arthas 会自动列出当前服务器上所有正在运行的 Java 进程列表(包含 PID、名称、启动时间等)。
  2. 选择目标:找到你的 Spring Boot 应用对应的 PID
  3. 一键接入:输入该 PID 数字并回车。

成功! 此时你将进入 Arthas 的 CLI 交互界面(看到 arthas> 提示符),即可开始执行 dashboardtracewatch 等强大的诊断命令。

注意:推荐下载 arthas-bin.zip 完整包。如果仅下载 arthas-boot.jar,在首次运行时需联网下载其余依赖文件,可能导致启动缓慢或在无网环境下无法使用。

三、核心命令实战

以下是 Arthas 最常用的核心命令,每个命令都搭配「使用场景+执行命令+真实输出示例+指标解读」,结合 Spring Boot 实际业务场景,让你看完就会用。

3.1 trace

使用场景:接口响应变慢,想知道方法调用链路中,哪个环节耗时最长(比如 DAO 层查询、工具类方法、第三方调用等)。

核心命令:

# 基础用法:追踪指定方法的调用链路及耗时
trace com.infree.meeting.service.common.FileService findCatFileList

# 进阶用法:只显示耗时超过 10ms 的调用(过滤无意义的快速调用)
trace com.infree.meeting.service.common.FileService findCatFileList -j 10

输出示例:

Affect(class count: 2 , method count: 2) cost in 702 ms, listenerId: 1
`---ts=2026-03-06 18:30:32.225;thread_name=http-nio-80-exec-5;id=65;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@34525143
    `---[152.3416ms] com.infree.meeting.service.common.FileService$$EnhancerBySpringCGLIB$$b2807281:findCatFileList()
        `---[99.88% 152.1547ms ] org.springframework.cglib.proxy.MethodInterceptor:intercept()
            `---[98.59% 150.0168ms ] com.infree.meeting.service.common.FileService:findCatFileList()
                +---[2.30% 3.4434ms ] com.infree.meeting.service.prepare.MeetingUserService:findMeetingUsByDevice() #1466
                +---[0.01% 0.0175ms ] com.infree.meeting.common.enums.FileEnum$Type:getCode() #1467
                +---[4.25% 6.3792ms ] com.infree.meeting.mapper.common.FileMapper:selectCatFileList() #1467
                +---[0.06% min=0.0019ms,max=0.0163ms,total=0.0973ms,count=18] com.infree.meeting.common.enums.CommonCacheEnum$Key:getCode() #1495
                +---[0.07% min=0.0023ms,max=0.0166ms,total=0.1086ms,count=18] com.infree.meeting.common.cache.CommonStringCache:get() #1495
                +---[0.06% min=0.0019ms,max=0.0117ms,total=0.0863ms,count=18] com.infree.meeting.common.enums.FileEnum$ConvertStatus:getCode() #1498
                +---[0.09% min=0.0016ms,max=0.0101ms,total=0.1403ms,count=36] com.infree.meeting.common.enums.FileEnum$ConvertStatus:getCode() #1499
                +---[0.09% min=0.0031ms,max=0.0185ms,total=0.1346ms,count=18] com.infree.meeting.utils.file.FileUtils:getFileSuffix() #1500
                +---[0.44% min=0.0158ms,max=0.0722ms,total=0.6629ms,count=18] com.infree.meeting.utils.ip.IpUtils:getProjectAddr() #1509
                +---[0.06% min=0.002ms,max=0.0112ms,total=0.0897ms,count=18] com.infree.meeting.entity.common.VsFile:<init>() #1513
                +---[0.05% min=0.0019ms,max=0.0098ms,total=0.0789ms,count=18] com.infree.meeting.entity.common.VsFile:setBusinessId() #1514
                +---[0.06% min=0.0015ms,max=0.016ms,total=0.0885ms,count=18] com.infree.meeting.common.enums.FileEnum$Type:getCode() #1515
                +---[0.05% min=0.0017ms,max=0.0114ms,total=0.0778ms,count=18] com.infree.meeting.entity.common.VsFile:setType() #1515
                +---[7.65% min=0.4754ms,max=0.9953ms,total=11.4707ms,count=18] com.infree.meeting.mapper.common.FileMapper:selectVsFileList() #1516
                +---[42.36% min=0.0033ms,max=63.4455ms,total=63.5491ms,count=18] cn.hutool.core.collection.CollectionUtil:isNotEmpty() #1517
                +---[13.30% min=0.5299ms,max=6.8333ms,total=19.956ms,count=18] com.infree.meeting.service.prepare.SxPiZhuService:selectByDeviceNameAndFileId() #1523
                +---[0.12% min=0.0022ms,max=0.1077ms,total=0.1844ms,count=18] com.infree.meeting.entity.prepare.MeetingUs:getId() #1532
                +---[12.40% min=0.3521ms,max=10.2649ms,total=18.6091ms,count=18] com.infree.meeting.mapper.prepare.SxPiZhuMapper:selectMarkByFileIdAndUsId() #1532
                +---[0.09% min=0.0026ms,max=0.0448ms,total=0.1294ms,count=18] com.infree.meeting.entity.terminal.CommentHandwriting:<init>() #1553
                +---[0.06% min=0.0022ms,max=0.0151ms,total=0.0848ms,count=18] com.infree.meeting.entity.prepare.MeetingUs:getUserId() #1554
                +---[0.05% min=0.002ms,max=0.017ms,total=0.0812ms,count=18] com.infree.meeting.entity.terminal.CommentHandwriting:setMeetingUserId() #1554
                +---[0.05% min=0.0017ms,max=0.0104ms,total=0.0766ms,count=18] com.infree.meeting.entity.prepare.MeetingUs:getMeetingId() #1555
                +---[0.05% min=0.0018ms,max=0.0122ms,total=0.0773ms,count=18] com.infree.meeting.entity.terminal.CommentHandwriting:setMeetingId() #1555
                +---[0.05% min=0.0019ms,max=0.0106ms,total=0.0755ms,count=18] com.infree.meeting.entity.terminal.CommentHandwriting:setSourceFileId() #1556
                `---[13.18% min=0.4672ms,max=7.9636ms,total=19.7788ms,count=18] com.infree.meeting.service.prepare.SxPiZhuService:findHandwritingFile() #1557

输出指标详细解读(重点关注耗时占比和异常值):

  • +---:表示调用树中的子节点,即该方法被上层方法调用(比如 isNotEmpty() 被 findCatFileList() 调用);
  • 42.36%:该方法(isNotEmpty())的总耗时占其直接父方法(findCatFileList())总耗时的 42.36%,占比极高,需重点关注;
    • 异常点:isNotEmpty() 是 Hutool 的集合判空工具方法,理论上应极快,如此高的占比,说明父方法可能被循环调用,或存在系统层面的停顿;
  • min=0.0033ms/max=63.4455ms:18 次调用中,最快 0.0033ms(符合预期),最慢 63.4455ms(异常);
    • 异常原因:可能是 JVM 发生 GC 停顿、CPU 资源争抢、SafePoint 阻塞等系统层面问题(Hutool 工具方法本身无问题);
  • total=63.5491ms:18 次调用的累加总耗时,几乎全部由一次 63ms 的异常调用贡献;
  • count=18:该方法在本次请求链路中被调用 18 次,若为单个 HTTP 请求,可能存在循环调用(如 for 循环中反复判空);
  • #1517:该方法在源码中的大致行号,可结合 jad 命令查看对应代码。

3.2 watch

使用场景:接口返回异常(如返回 null、错误码),想快速查看方法的入参是否正确、返回值是否符合预期,或是否抛出异常(无需修改代码加日志重启)。

核心命令:

watch com.example.demo.service.UserService findUser '{params, returnObj, throwExp}' -x 3

参数说明:-x 3 表示展开对象的深度为 3,默认深度为 1,可能无法看到对象内部字段(如 DTO 的属性值);按 Ctrl+C 可停止观察。

输出示例:

method=com.infree.meeting.controller.prepare.MeetingPrepareController$$EnhancerBySpringCGLIB$$bab4132c.editMeetingOrder location=AtExit
ts=2026-03-06 18:46:30.179; [cost=29.1456ms] result=@ArrayList[
    @Object[][
        @MeetingPmDto[
            id=@Long[552515700664823808],
            roomId=null,
            meetingTitle=@String[第一研讨],
            meetingStage=null,
            chairman=@String[王豪],
            startTime=@String[2026-03-05 15:39],
            endTime=@String[2026-03-05 16:39],
            isEncryptFile=null,
            templateId=null,
            isTemplate=null,
            unionRoomId=null,
            projectId=null,
            parentId=null,
            classification=@Integer[4],
        ],
    ],
    @AjaxResult[
        serialVersionUID=@Long[1],
        code=@Integer[200],
        msg=@String[更新研讨信息成功],
        data=null,
    ],
    null,
]

输出指标详细解读

  • cost=29.1456ms:该方法总执行耗时为 29.1456ms,属于常规接口耗时,无性能问题;
  • result=@ArrayList[...]:对应命令中 {params, returnObj, throwExp} 的组合结果,ArrayList 的 3 个元素依次为:
    • 第一个元素:@Object[][] → 方法入参(params),此处为 MeetingPmDto 对象,可清晰看到入参的具体属性值;
    • 第二个元素:@AjaxResult[...] → 方法返回值(returnObj),此处返回 code=200、msg=更新成功,符合预期;
    • 第三个元素:null → 方法异常(throwExp),null 表示方法执行无异常,若有异常会显示完整异常堆栈。

3.3 jad

使用场景:线上代码运行异常,但本地代码测试正常,怀疑线上代码与本地代码不一致(如遗漏提交、版本回滚不彻底),可通过 jad 命令反编译线上类,查看实际运行的代码。

核心命令:

jad com.infree.meeting.service.common.FileService

输出示例:

ClassLoader:
+-java.net.URLClassLoader@710726a3

Location:
/C:/Users/74727/IdeaProjects/meeing-manager3/target/classes/

package com.infree.meeting.service.common;

import cn.com.infree.office.ConvertLibrary;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.infree.meeting.common.cache.CommonStringCache;
import com.infree.meeting.common.cache.ConvertPdfCache;

关键解读:输出中会显示类的类加载器、所在路径,以及完整的反编译代码,对比本地源码即可判断线上代码是否一致;若不一致,需排查部署流程问题(如未提交代码、部署包错误)。

3.4 retransform

使用场景:线上出现小bug(如参数判断错误、逻辑疏漏),重启服务会影响业务,可通过 retransform 命令动态替换类文件,紧急修复后,后续再重启服务恢复。

注意:高风险操作,仅适用于紧急修复简单bug,复杂逻辑修改可能导致系统异常,操作前务必备份类文件和服务数据。

操作步骤

1.本地修改代码,编译生成对应的 .class 文件(确保与线上类的全限定名一致);将 .class 文件上传至服务器(如 C:\\test\\FileService.class);

2.执行 Arthas 命令,动态替换类:

retransform C:\\test\\FileService.class

3.验证效果后,务必重启服务恢复。

3.5 ognl

使用场景:线上需要临时执行某个方法(如重置缓存、清理无效数据、触发特定业务逻辑),无需通过接口调用,可直接通过 ognl 命令执行。

核心命令:

# 示例1:执行静态方法,重置缓存
ognl '@com.infree.meeting.common.cache.CommonCache@commonMap.clear()'

# 示例2:指定类加载器执行(Spring Boot 项目常用,避免类加载器异常)
# 1. 先查看类的类加载器 ID
sc -d com.infree.meeting.common.cache.CommonCache
# 2. 用类加载器 ID 执行方法(-c 后接类加载器 ID)
ognl -c 710726a3 '@com.infree.meeting.common.cache.CommonCache@add("myKey", "123")'

关键说明:Spring Boot 项目的类通常由 LaunchedURLClassLoader 加载,而非默认的 Bootstrap ClassLoader,若直接执行 ognl 命令提示类找不到,需通过 sc -d 类全限定名 查看类加载器 ID,再用 -c 参数指定类加载器。

3.6 tt

使用场景:线上方法调用失败(如偶发异常),无法实时捕捉调用现场,可通过 tt 命令记录方法调用的所有信息(参数、返回值、异常、耗时),事后回放调用,复现问题场景。

tt 命令的核心优势:无需实时监控,可事后回放调用,尤其适合排查偶发bug、难以复现的问题。

核心用法(分步骤)

  1. 开始记录方法调用:
# 基础用法:监听指定方法的所有调用,记录到时间隧道
tt -t com.example.demo.controller.UserController getUser

# 进阶用法:监听方法,限制记录次数(防止内存溢出),展开对象深度3
tt -t com.example.demo.service.OrderService createOrder -n 10 -x 3

参数说明:-t 开启记录模式;-n 10 只记录最近10次调用(生产环境必加,避免内存爆满);-x 3 展开对象深度。

  1. 查看记录列表:
# 查看时间隧道中所有记录
tt -l

输出示例:

INDEX  TIMESTAMP          COST(ms  IS-RE  IS-EXP  OBJECT        CLASS                      METHOD
                           )        T
-----------------------------------------------------------------------------------------------------------------------
 1000   2026-03-06 20:40:  58.3923  true   false   0x56dae6      FileService                findCatFileList
        24.216
 1001   2026-03-06 20:40:  74.2002  true   false   0x38c2c4b5    FileService$$EnhancerBySp  findCatFileList
        24.231                                                   ringCGLIB$$bd5a0fc7
Affect(row-cnt:2) cost in 1 ms.

字段解读:

    1. INDEX:记录的唯一索引 ID(回放时必须使用);
    2. COST:方法执行耗时;
    3. IS-RET:是否正常返回(true=正常,false=异常);
    4. IS-EXP:是否抛出异常(true=异常,false=正常)。
  1. 查看记录详情:
# 查看索引为 1002 的异常记录详情
tt -i 1002

输出内容包含:方法签名、调用位置、入参列表、返回值(正常返回时)、异常堆栈(异常时),可快速定位异常原因。

  1. 回放调用(核心功能):
# 简单回放:使用原参数再次执行该方法
tt -p 1002

# 交互式回放:修改参数后再执行(高级用法)
tt -p 1002 --interactive
  1. 清理记录:
# 删除指定索引的记录
tt -d 1000

# 清空所有记录,释放内存
tt -c
  1. 检索记录:

从已记录的列表中筛选数据。

# 搜索所有抛出异常的记录
tt -s 'isExp==true'

# 搜索耗时大于 100ms 的记录
tt -s 'cost>100'

# 搜索参数中包含 "userId=123" 的记录 (OGNL 表达式)
tt -s 'params[0].userId==123'

3.7 JVM 监控

线上服务出现卡顿、内存溢出、CPU 飙升等问题,核心是要先掌握 JVM 运行状态,Arthas 提供了 3 个核心命令,可全面监控 JVM 的线程、内存、GC 情况。

3.7.1 dashboard

使用场景:快速概览 JVM 线程、内存、GC、运行时信息,类似于 Linux 的 top 命令,实时刷新(默认每5秒),适合快速判断服务整体状态。

核心命令dashboard

输出示例:

ID   NAME                          GROUP          PRIORITY  STATE    %CPU      DELTA_TIM TIME      INTERRUPT DAEMON
95   DestroyJavaVM                 main           5         RUNNABLE 0.0       0.000     0:21.187  false     false
-1   VM Thread                     -              -1        -        0.0       0.000     0:10.656  false     true
24   RMI TCP Accept-0              system         5         RUNNABLE 0.0       0.000     0:3.906   false     true
-1   C1 CompilerThread11           -              -1        -        0.0       0.000     0:1.750   false     true
-1   VM Periodic Task Thread       -              -1        -        0.0       0.000     0:1.718   false     true
9    IntelliJ Suspend Helper       main           5         TIMED_WA 0.0       0.000     0:1.437   false     true
-1   C1 CompilerThread9            -              -1        -        0.0       0.000     0:1.359   false     true
-1   C1 CompilerThread8            -              -1        -        0.0       0.000     0:1.359   false     true
-1   C1 CompilerThread10           -              -1        -        0.0       0.000     0:1.250   false     true
70   http-nio-80-exec-7            main           5         WAITING  0.0       0.000     0:0.765   false     true
136  arthas-NettyHttpTelnetBootstr system         5         RUNNABLE 0.0       0.000     0:0.734   false     true
Memory                    used    total    max     usage    GC
heap                      571M    953M     7225M   7.91%    gc.ps_scavenge.count          11
ps_eden_space             493M    508M     2635M   18.74%   gc.ps_scavenge.time(ms)       180
ps_survivor_space         27M     36M      36M     75.09%   gc.ps_marksweep.count         3
ps_old_gen                49M     409M     5419M   0.92%    gc.ps_marksweep.time(ms)      372
nonheap                   130M    137M     -1      95.01%
code_cache                33M     33M      240M    13.75%
metaspace                 86M     92M      -1      93.80%
compressed_class_space    10M     12M      1024M   1.07%
Runtime
os.name                                                     Windows 11
os.version                                                  10.0
java.version                                                1.8.0_411
java.home                                                   C:\Program Files\Java\jdk-1.8\jre
systemload.average                                          -1.00
processors                                                  20
timestamp/uptime                                            Fri Mar 06 20:09:05 CST 2026/3666s

输出解读(核心3个面板)

  1. 线程面板
    1. %CPU 均为 0.0%:服务当前 CPU 负载极低,无高占用线程;
    2. 关键线程:DestroyJavaVM(JVM 主线程)、http-nio-80-exec-7(Tomcat 请求处理线程,WAITING 状态表示空闲等待请求)、arthas 相关线程(正常);
    3. 结论:无线程阻塞、死锁,线程池资源充足。
  1. 内存面板
    1. 堆内存(heap):使用率 7.91%(571M/7225M),空间充裕,无内存压力;
    2. 分代内存:Eden 区(18.74%)、Survivor 区(75.09%)、老年代(0.92%)均正常,无内存泄漏迹象;
    3. GC 统计:Minor GC 11 次、Full GC 3 次(服务运行1小时),频率极低,GC 策略正常;
    4. 元空间(metaspace):使用率 93.80%,看似偏高,但实际使用 86M,属于开发环境正常范围(生产环境若持续上涨,需调整 Metaspace 配置)。
  1. 运行时面板:显示操作系统、JDK 版本、CPU 核心数、服务运行时长等信息,可快速排查环境适配问题。

3.7.2 heapdump

使用场景:服务出现内存泄漏、OOM 异常,需生成堆内存快照(.hprof 文件),后续用 MAT 等工具分析,定位内存泄漏的根源(如未释放的对象、大对象堆积)。

核心命令:

# 生成堆内存快照,保存到指定路径
heapdump C:\\test\\dump.hprof

关键说明: 生成的 .hprof 文件大小与 JVM 堆内存配置相关(通常为堆内存的 1~1.5 倍),生成前需确保服务器磁盘空间充足;生成过程会触发短暂 STW(服务停顿),建议在业务低峰期执行;后续可将 .hprof 文件下载到本地,拖到IDEA中进行分析,定位内存泄漏点。

3.7.3 thread

使用场景:CPU 飙升、服务卡顿、死锁等线程相关问题,可通过 thread 命令快速定位异常线程,查看线程堆栈。

核心命令:

# 1. 查看 CPU 占用最高的前 3 个线程(定位 CPU 飙升问题)
thread -n 3

# 2. 排查死锁,找出阻塞其他线程的线程
thread -b

# 3. 查看指定 ID 线程的详细堆栈(深入分析异常线程)
thread 105

# 4. 概览所有线程状态,了解线程分布
thread

输出示例(thread -n 3):

Threads Total: 58, NEW: 0, RUNNABLE: 3, BLOCKED: 0, WAITING: 15, TIMED_WAITING: 40, TERMINATED: 0
ID              NAME                                      GROUP          PRIORITY   STATE      CPU%       TIME
105             http-nio-8080-exec-6                      main           5          RUNNABLE   45.2       12s
106             http-nio-8080-exec-7                      main           5          RUNNABLE   32.1       8s
102             C1 CompilerThread0                        system         9          RUNNABLE   12.5       45s
---
"http-nio-8080-exec-6" Id=105 RUNNABLE
    at com.infree.meeting.service.common.FileService.findCatFileList(FileService.java:150)
    at com.infree.meeting.controller.FileController.list(FileController.java:45)
    ...

输出解读: 输出的 3 个线程均为 JVM 内部核心线程(Reference Handler、Finalizer、Signal Dispatcher),负责 JVM 自身的引用回收、对象收尾、信号处理,无业务线程;cpuUsage=0.0%:所有线程无 CPU 消耗,说明服务当前处于低负载状态,无 CPU 飙升问题;线程状态:WAITING(等待状态,正常空闲)、RUNNABLE(可运行状态,无实际消耗),无线程阻塞、死锁。

补充:线程状态对照表(快速判断线程异常):

线程状态

含义

常见原因

NEW

新建

线程对象已创建,但未调用 start() 方法

RUNNABLE

运行中

正在执行代码,或等待 CPU 时间片(CPU 高占用多为此状态)

BLOCKED

阻塞

等待获取 synchronized 锁(死锁高发状态)

WAITING

无限等待

等待其他线程执行特定操作(如 Object.wait()、Thread.join())

TIMED_WAITING

超时等待

等待指定时间(如 Thread.sleep()、LockSupport.parkNanos())

TERMINATED

终止

线程已执行完毕

正文到此结束