ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Java线上排查利器Arthas实战:从CPU飙高到接口超时

Java线上排查利器Arthas实战:从CPU飙高到接口超时

在Java后端摸爬滚打久了,你大概率经历过这样的场景:线上应用突然CPU飙高、接口超时、用户反馈某个功能时好时坏,可日志里干干净净,一点异常都没打出来。重启大法虽然能暂时缓解,但根本问题还在,而且重启以后什么现场都没了。这种时候,我第一反应就是开Arthas,这个工具我第一次用就觉得“真香”,甚至可以说,它已经成了我排查线上问题的默认入口。所有要连着好几台机器、翻各种日志、反复猜测代码逻辑的日子,在Arthas面前基本可以画上句号了。

Arthas是Java生态里非常出名的在线诊断工具,它最大的特点是不用改代码、不用重启应用,直接附着在运行中的JVM上,就能实时查看类加载信息、方法调用参数、返回值、异常,甚至还能动态修改内存中已加载的类,重新定义类逻辑。对于需要快速定位生产环境问题的开发、运维和SRE同学来说,这是一把不折不扣的瑞士军刀。这篇内容我会把日常用得最多、最顺手的功能串起来讲一遍,从怎么快速附着到具体命令怎么用、遇到问题的排查思路是什么,尽量按照实战场景来写,方便你直接照葫芦画瓢。

1. 为什么Arthas值得“真香”,先搞清楚它解决的痛点

1.1 传统排查方式的困境

我最早排查线上问题,基本靠三板斧:看日志、加临时日志重新发版、重启试试。日志如果没有打印出关键堆栈,就得靠猜;加日志发版,光是走流程就要很久,而且改了代码的行为可能就和问题现场不一致了,有些偶发问题根本复现不出来;重启就更不用提了,JVM里的现场被清得一干二净,线程状态、堆内存数据、正在执行的调用链,全都没了。

更难受的是定位性能问题。一个接口耗时忽高忽低,你怀疑某个第三方调用超时,但没法确认到底卡在哪一行。最原始的方案是到处打时间戳,然后重新发版,算下来一个下午就过去了。而且发版本身会引入变量,问题可能就这次发完消失了,下次出问题又不知道什么时候。

Arthas这类工具的思路完全不同:应用不需要重启,它会在目标JVM进程上启动一个代理,然后把诊断命令通过socket传给这个代理,代理用字节码注入技术去拦截目标类的方法,把你想看的参数、返回值、异常、执行耗时实时抓回来。相当于给运行中的应用做了一次“无痛内窥镜”检查,随时都能做,做完拔掉,恢复原样。

1.2 和BTrace、Greys等同类工具的取舍

其实在Arthas之前,已经有BTrace这类早期工具,我也试用过。BTrace有一个很大的问题是使用门槛比较高,需要编写专门的脚本,而且对版本、对JVM参数比较敏感,用得不好容易把自己搞挂。Arthas虽然也用了类似字节码增强的原理,但它在交互体验和命令设计上做了很多工程化的打磨,像看板一样实时刷新的dashboard、一键查看线程CPU占用的thread命令、跟踪方法调用链的trace命令,都是直接敲命令就能用,非常符合后端开发的直觉。

另外,Arthas对Java版本和中间件环境的兼容性做得不错,像Spring Boot这类框架的项目几乎开箱即用,不需要额外改启动参数。所以很多团队会把Arthas作为标准线上问题排查工具来推广,甚至要求每个服务镜像里都预留Arthas的安装路径,方便随时附着排查。

提示:Arthas本身是一个诊断工具,它不改变业务代码的最终逻辑。但要注意,在敏感环境使用时需要做好权限管理,避免有人通过它执行危险操作,比如动态修改类逻辑。生产环境建议配合审计和权限体系来用。

2. 五分钟上手Arthas:安装、启动与附着

2.1 用官方脚本快速安装

Arthas的安装方式很灵活,最省事的方法是直接下载官方提供的as.sh脚本,然后执行:

curl -L -O https://github.com/alibaba/arthas/releases/latest/download/arthas-boot.jar java -jar arthas-boot.jar

这里我个人更推荐直接下载arthas-boot.jar来用,因为后续版本升级时,用它来引导会更统一,也方便自定义下载目录。脚本方式适合快速体验,它的本质也是下载arthas-boot.jar再启动。

启动之后,命令行会列出当前机器上所有的Java进程,就像这样:

[INFO] arthas-boot version: 3.x.x [INFO] Found existing java process, please choose one and input the serial number: * [1] 12345 xxx-order-service [2] 23456 xxx-gateway

输入序号回车,就能附着到对应的进程上。需要注意,当前登录用户必须和目标进程的启动用户一致,否则会附着失败或者出现权限不足的问题。这个坑我踩过很多次,后面会在排查章节单独说。

2.2 无交互式启动:适合脚本和自动化场景

如果你需要在CI/CD流水线或者自动化脚本里启动Arthas,就不能用交互式选择进程的方式。这时候可以直接指定进程ID启动:

java -jar arthas-boot.jar --attach-only --target-ip 127.0.0.1 -p 12345

或者直接用as.sh带参数:

./as.sh -p 12345

还有一种场景是应用本身就集成了Arthas的agent方式启动,即在JVM参数里加上-javaagent:arthas-agent.jar,这样应用一跑起来,Arthas的attach通道就已经准备好了,需要诊断的时候直接用客户端连接即可。这种方式特别适合容器环境,因为容器的PID 1进程往往不是Java进程,动态attach会受PID namespace影响,提前挂agent反而更省心。

2.3 理解Arthas的attach与Agent机制

Arthas底层用的是Java的Attach API。它向目标JVM发送attach请求,并加载一个agent,这个agent会和Arthas的服务端建立通信,服务端再开一个telnet端口(默认8563)以及一个HTTP端口(默认8562)。你敲的每一条命令,都会先经过本地的Arthas客户端,再通过这个通道发送给目标JVM里的agent去执行。

这条链路听起来有点绕,但只要理解了,排错就快多了。比如你遇到attach成功但命令半天没反应,那大概率是目标进程中agent初始化出了问题,比如类加载器冲突、Arthas版本过旧等。再比如你发现本地能连上自己的测试环境,但线上环境始终连接失败,就要检查网络策略是否开放了8563和8562这两个端口。

2.4 检查Arthas是否工作正常

附着成功后,可以先执行一个最简单的命令试试:

[arthas@12345]$ version

如果返回版本号,说明链路通畅。然后可以执行:

[arthas@12345]$ sysprop

查看系统属性。这些都正常了,再进入正式的诊断流程。

3. 高频命令实战解析:从看板到方法级追踪

3.1 dashboard:实时全局看板

dashboard是我每次进入Arthas后几乎必敲的第一条命令。它会以一个动态面板的形式展示当前JVM的概况,包括线程、内存、GC、类加载数量以及运行时环境信息。看板会自动刷新,默认每3秒刷新一次,当然你可以用-i参数指定刷新间隔,比如:

[arthas@12345]$ dashboard -i 1000

这样每秒刷新一次。下面给你看一眼典型输出长什么样:

ID NAME GROUP PRIORITY STATE %CPU TIME INTERRUPTED DAEMON 67 xx-rpc-thread-14 main 5 RUNNABLE 33 0:23 false true ... Memory used total max usage GC heap 1.2G 2.0G 4.0G 30.00% gc.ps_scavenge.count: 128 ...

看板里最值得关注的是CPU占用排前几的线程。如果某条业务线程长期处于RUNNABLE状态,而且CPU占比明显偏高,基本可以锁定它就是CPU飙高问题的元凶。再用thread -n命令抓它的线程栈,就能进一步确认卡点位置。

3.2 thread:线程排查利器

thread命令有好几个重要的用法,按实用频率排,大概是这么个顺序:

第一,查看当前最耗CPU的线程栈:

[arthas@12345]$ thread -n 5

这个命令会把CPU占用最高的5个线程的堆栈信息全部打印出来,并且自动带上线程的ID、名称、CPU使用率、状态。有一次我遇到线上CPU突然打满,用这个命令一下就看到了某个线程卡在了一个JSON序列化工具的循环里,栈顶清清楚楚写着某行代码的toString调用,问题原因秒定位。

第二,查看指定线程的完整栈:

[arthas@12345]$ thread 67

其中67就是线程ID。这会打印该线程的完整堆栈,适合排查线程阻塞在哪里、锁等待在哪里。第三,查找线程间是否有锁等待和死锁:

[arthas@12345]$ thread --state=BLOCKED

如果想要检查死锁,Arthas没有专门的一条命令,但是可以结合thread --state=BLOCKED和线程栈中的“waiting for monitor”字样来判断。某次我处理过一个偶发死锁问题,两个线程都在等对方的锁,通过这个命令一查,堆栈里两个线程互相等待的锁对象非常直观,最后定位到是加锁顺序不一致导致的经典死锁。

3.3 jad:反编译线上类,确认跑的是不是你以为的代码

线上最尴尬的情况之一,就是代码看着是新的,实际发出去的包是旧的。与其问别人,不如直接反编译看字节码。jad命令能反编译任意已加载类,打印出它的Java源码结构(实际是反编译结果,可能和原始源码有细微差别,但逻辑基本可读)。

用法非常简单:

[arthas@12345]$ jad com.example.OrderService

输出会带类加载器信息、源码位置、反编译的Java代码。这个命令在排查“为什么线上行为和本地不一致”时特别有用。有一次客户端反馈某功能表现异常,我用jad一查,发现类里根本没有最新加的判断逻辑,立刻确认是发布流程出了问题,省掉了无休止的讨论。

如果你的类被JIT优化了,反编译出来可能有部分内容显示不出来,但不影响判断大致逻辑。可以用jad --source-only只输出源码,方便复制到本地做diff比较。

3.4 watch:观察方法调用入参、返回值和异常

watch是Arthas里我最喜欢的命令,没有之一。它用来观察指定方法的每一次调用,抓取入参、返回对象、抛出的异常、耗时等。写法如下:

[arthas@12345]$ watch com.example.OrderService createOrder '{params, returnObj, exception, cost}' -x 3

拆解一下这个命令:

  • 第一个参数是类全限定名。
  • 第二个参数是方法名。
  • 第三个参数是OGNL表达式,决定要输出什么。'{params, returnObj, exception, cost}'分别代表入参列表、返回对象、异常、耗时。这个大括号不是列表字面量,而是输出多个字段的组合。
  • -x表示展开对象的层级,数字越大展开细节越多。比如-x 3能看到入参对象的三层嵌套结构。

这个命令在排查“参数对了却得到奇怪结果”、“某个方法偶尔抛异常但日志没记录”的场景下,几乎是精准制导。比如有次同行遇到一个接口偶尔返回错误码,但代码里没打日志,我建议用watch带异常参数去抓,结果没等多久就抓到了异常栈,是一个特殊的非法参数组合导致的,本地测试完全没覆盖到。

注意点:watch默认只监控方法进入时的信息,如果你想额外看方法退出时的结果,要加上-b或-e参数来控制位置,或者更常用的是保持默认,然后在OGNL表达式里同时取返回值和异常即可。Arthas的做法是默认在方法结束以后统一输出,所以直接查returnObj和exception是能拿到的。

3.5 trace:定位方法内部耗时分布

有时候接口慢,但你不知道热点是在内部哪个子调用上。trace命令可以追踪一个方法内部的所有子调用,输出每一层的耗时,以及调用次数。

[arthas@12345]$ trace com.example.OrderService createOrder '#cost > 200'

这个命令的含义是,当createOrder整体耗时超过200毫秒时,打印方法内部的调用链耗时详情。每一行会显示类名、方法名、耗时、调用来源。

trace是我处理性能问题时最依赖的工具。比如你怀疑数据库查询慢,但数据库监控又没查到慢SQL;或者怀疑HTTP外部服务超时,却不知道具体在哪个阶段。trace一跑,整个方法内部的每一步耗时一目了然,是内存计算慢,还是RPC调用慢,还是某个循环里做了太多无用计算,全都能看到。

和watch不同的是,trace的成本相对高一些,尤其是在高频调用方法上开启trace,会有一定的性能开销。所以在生产环境使用时要控制好追踪的目标方法,不要挂在整个入口上大规模trace,更推荐在定位到大致方法后,细粒度去开trace。

3.6 stack:方法被谁调用、调用链长什么样

stack命令会输出当前方法被调用的调用路径,简单理解就是“谁调用了我”。用法:

[arthas@12345]$ stack com.example.OrderService createOrder

这个命令适合回答“这个方法为什么被反复触发”、“这个入口是被人调用还是内部定时任务触发的”。有一次排查一个数据错乱问题,就是想弄清楚某条数据为什么会走到一个异常分支,最后用stack发现原来是消息消费逻辑里间接调用了同一个方法,导致数据状态被重复修改。

stack还支持按条件输出,比如只输出耗时超过阈值的调用:

[arthas@12345]$ stack com.example.OrderService createOrder '#cost>1000'

这样既能看到调用链,也能筛选出慢调用,用来排查偶发性慢请求特别好使。

3.7 tt:时间隧道,记录方法调用的历史现场

如果说watch是“实时看每一次调用”,那tt就是“把调用的现场记下来,回头慢慢看”。tt命令能记录一段时间内某个方法的调用快照,包含入参、返回、异常、调用时间等,并且你能用tt的Play功能去重放某一次调用。

基本用法:

[arthas@12345]$ tt -t com.example.OrderService createOrder -n 20

意思是记录最近20次createOrder调用的快照。输出里每行有一个ID,如果你想看第8次调用的具体参数和返回值:

[arthas@12345]$ tt -i 8

有一次线上偶发问题最让人头疼的就是无法复现,当时我用tt挂了半小时,记录到了那一次异常调用的完整入参,然后用这个参数在测试环境一跑就复现了,整个排查从“玄学”变成了“确定性”。

tt的代价比watch更大,因为它要在方法出入口都插入拦截逻辑,并且保存快照。所以开启时间不要过长,记录次数也要限制,比如-n 20,抓完立即关闭。

3.8 monitor:统计方法调用频率、成功率和耗时趋势

如果你想看一个方法在一段时间内的整体表现,比如每分钟被调多少次、成功率如何、平均耗时多少,monitor就是干这个的:

[arthas@12345]$ monitor com.example.OrderService createOrder -c 30

表示每30秒统计一次createOrder的调用次数、失败次数、失败率、平均耗时等。这个命令适合不需要看单个调用细节,而是想看宏观趋势的场景。比如压测过程中观察某个方法的TPS、RT变化,或者线上某个接口失败率突然升高时先确认是不是这个原因。

3.9 sc、getstatic与ognl:查看类信息、读取静态变量、执行任意表达式

这三个命令放到一起讲,因为它们是Arthas里“探查类结构”的组合拳。

sc用来搜索所有已加载的类:

[arthas@12345]$ sc *OrderService*

输出会显示类全限定名、类加载器、类状态。如果想看类的详细结构,加-d参数:

[arthas@12345]$ sc -d com.example.OrderService

这样会输出类的修饰符、注解、父类、接口、成员变量等。

getstatic用来读取类的静态变量:

[arthas@12345]$ getstatic com.example.OrderService CACHE_SIZE

比如某个配置类的静态字段是在启动时加载的,又没办法动态刷新,用getstatic能直接看到当前内存里的值,判断配置是否真的生效了。

ognl是最灵活的命令,它能在目标JVM里执行任意表达式。比如调用某个静态方法、获取某个对象的字段、甚至修改某个字段的值:

[arthas@12345]$ ognl '@com.example.GlobalConfig@getConfig()'

也可以带上下文执行,比如获取当前线程上下文的某个Bean:

[arthas@12345]$ ognl -x 2 '#context = @org.springframework.web.context.ContextLoader@getCurrentWebApplicationContext(), #bean = @context.getBean("orderService"), #bean.queryStatus()'

ognl的“任意执行”能力非常强,但也要格外小心,它相当于给了你在生产环境执行代码的权限。尤其是在写修改类型表达式时,一定要仔细确认,不要改错字段。

3.10 mc与redefine:动态更新类逻辑

redefine是我用得比较谨慎但关键时刻能救命的命令。它允许你修改已加载类的字节码,相当于在运行中“热更新”类的逻辑。基本流程是:先用jad把目标类反编译出来,导出源码到本地;改完逻辑以后,用mc命令编译成.class文件;再用redefine命令把它加载进JVM。

步骤如下:

[arthas@12345]$ jad --source-only com.example.OrderService > /tmp/OrderService.java # 然后在本机编辑 /tmp/OrderService.java,改好逻辑 [arthas@12345]$ mc /tmp/OrderService.java -d /tmp/output [arthas@12345]$ redefine /tmp/output/com/example/OrderService.class

注意事项:redefine只改变后续新执行的逻辑,对已经执行到一半的调用是无效的;而且redefine不会改变类的结构,你不能新增字段、不能新增方法签名、不能增加销毁原结构。一般我拿它来做临时补丁,加一个日志输出、调一个参数值等等,等新的版本发上去以后,就不要再依赖redefine的临时逻辑了。

警告:redefine本质上是修改运行中的JVM内存里的类定义,风险等级很高,只适合临时应急。不到万不得已,我建议你先尝试用ognl修改配置值、用dashboard观察趋势,最后才考虑redefine。而且redefine前一定要把原始类文件备份好,保证能回滚。

4. 实战案例拆解:从CPU飙高到接口超时的排查全流程

4.1 案例一:CPU突然飙到500%的线程定位

事情大约是这样的:某跨平台订单服务的CPU在下午两点左右突然飙高,监控报警直接打到群里。按照以前的处理方式,第一件事是登录机器看top,找到CPU高进程的PID,然后jstack导出线程栈,再手动去找nid对应的线程。这一套流程本身没问题,但jstack的线程栈里全是十六进制线程ID,要对应到业务线程还得人工比对,效率很低。

有了Arthas之后流程变成了这样:先附着到目标进程,执行dashboard,马上就能看到哪个线程的CPU占用异常高,通常那个线程的ID还会反复出现在刷新面板的最前面。然后执行:

[arthas@12345]$ thread -n 3

Arthas会直接打印出CPU占用最高的三个线程的完整堆栈,自动把线程名称、CPU时间、堆栈都组装好了。那次我一眼就看到某个线程卡在一个XML解析方法内部,最底层是在JVM里做正则匹配,数据量一大,CPU直接被吃满。顺着栈里的业务类名,很快就对应到了代码行,发现是一个对超大字符串做了多次replaceAll的循环,属于典型的正则回溯问题。

这个案例关键点在于thread -n非常省事,省掉了jstack + 人工匹配nid的过程。如果只靠jstack,十几分钟能定位就算不错了;用Arthas,一两分钟就锁定了根因。

4.2 案例二:接口偶发超时,怎么抓到慢调用的具体位置

另一个印象深刻的案例是某个查询接口偶发超时,时好时坏。我先打开trace守护在入口方法上:

[arthas@12345]$ trace com.example.QueryService queryDetail '#cost > 1000'

跑了大约十分钟,抓到了一次耗时1200毫秒的调用快照。结果显示,大部分耗时花费在调用下游的某个HTTP接口上,时间约900毫秒,而我对这个下游HTTP接口的容错判断阈值是500毫秒,所以导致上游超时。顺着这个线索,检查了下游服务的响应时间分布,发现是对方服务在整点时刻做批量任务,导致该时段RT拉长。后续调整了调用方的超时时间,并且给这个下游调用增加了本地缓存,问题彻底解决。

如果不用trace,靠加日志排查这种偶发问题,可能要发好几个版本才能抓到一个慢样本,而且很有可能会因为随机性导致错过现场。trace的价值在于它不需要任何提前埋点,事后诊断能力很强。

4.3 案例三:方法返回值为什么和预期不一致

还有过一次经历,是同事在排查一个对账系统的金额计算差异。日志里打了入参,也打了最终返回值,但中间经过了好几个子方法的嵌套调用,看不出是哪一步把金额改错了。这种情况watch+stack组合拳非常管用。先watch最外层方法,看看入参和最终返回值;如果出入参没问题,就深入内部方法分别watch,找到第一个出现差异的那一层。

当时我们用watch单独盯了一个金额调整的方法,抓到的入参是一个对象,对象的某个金额字段是100.00,但returnObj返回时变成了99.99。再往下一层看,发现里面调用了一个舍入工具,舍入模式默认是向下取整,而业务方预期是四舍五入。顺着这个链路往下追,又用stack查了舍入工具方法的调用来源,确认只有这一条业务路径走错了舍入模式。整个过程,没有重启、没有改动一行代码,一个下午就把根因挖干净了。

5. 常见问题与排查思路:Arthas使用中踩过的坑

5.1 attach失败,提示Unable to open socket file

这个报错最常见的场景是:你用的系统账号和目标Java进程的启动账号不一致。比如应用是用service用户启动的,你sudo到root用户执行Arthas,那么Arthas无法读取目标JVM的临时目录文件,就会出现socket文件路径相关报错。

解决办法很直接:换成和启动进程相同的用户执行。如果一定要root操作,可以考虑安装Arthas到目标用户目录下,并用su切换到该用户后再启动。

5.2 所有命令都能执行,但watch/trace没有任何输出

这个有两种可能。一种是方法已经启动了很久没有被调用,你可以先用monitor确认调用是否真的存在。另一种是方法是在其他类加载器里加载的,而Arthas默认匹配的类加载器集合不对。特别是用到自定义类加载器、OSGi环境时,需要用sc -d确认类的类加载器信息,然后用classloader命令配合指定类加载器再观察:

[arthas@12345]$ classloader -l

然后指定类加载器的索引,再对目标方法执行watch。你也可以用watch时带上-c参数指定类加载器的hash值。当然,现实中多数Spring Boot应用是单一类加载器,基本不会遇到这类问题。

5.3 对象层级太深,输出被截断

默认情况下,Arthas为了避免把大量数据一股脑打印出来,会限制OGNL表达式输出对象的展开深度。如果你想看更多字段,用-x参数调大即可。比如:

[arthas@12345]$ watch com.example.OrderService createOrder '{params, returnObj}' -x 5

如果-x调得太大导致输出非常长,影响阅读,可以同时用-n限制观察次数,比如-n 3只输出三次结果。

5.4 ognl表达式写复杂了容易出错,怎么调试

ognl表达式在Arthas里虽然功能强大,但一旦写复杂了,语法错误可能比业务代码本身还难排查。我的习惯是先在本地用一个简单的Spring Boot应用试表达式,确认无误了再拿到生产环境执行。还有一个技巧是拆分成小的表达式逐步验证:先获取对象引用,再访问字段,不要一口气写超长的OGNL链条。

比如你想拿到Spring容器里的某个Bean并调用方法,可以先执行:

[arthas@12345]$ ognl '#ctx=@org.springframework.web.context.ContextLoader@getCurrentWebApplicationContext(), #ctx'

确认这个对象能拿得到,再去扩展后面的字段访问。

5.5 Arthas本身对性能的影响

很多人会担心Arthas附着之后影响业务性能。实话实说,开启watch/trace/monitor这类命令,会对目标方法做字节码增强,有一定额外开销。如果目标是低频方法,影响几乎可以忽略;如果目标是高QPS方法,性能损耗会明显一些。所以生产使用有几个原则:

  • 只在需要排查的时间窗口打开命令,用-n限制记录次数,用完马上关闭。
  • 不要同时开启大量方法的trace/watch。
  • 优先用dashboard和thread这类低开销命令做初步排查,锁定目标后再用watch/trace做精细分析。

5.6 Arthas版本太旧导致命令行为不一致

Arthas迭代速度比较快,一些命令的参数和输出格式在旧版本里可能不兼容。如果你发现某个命令行为和文档不一致,优先检查版本:

[arthas@12345]$ version

建议下载最新release版本,并保持目标环境和运维脚本里使用的版本一致,免得今天在这台机器上可用、明天换台机器就报错。

5.7 容器环境里attach的特殊情况

容器化部署越来越普遍,在容器里用Arthas有时会遇到PID namespace限制导致attach失败,或者容器里没有安装完整JDK只有JRE。解决办法是:

  • 基础镜像里提前安装JDK而不是只有JRE。
  • 使用Arthas的agent模式启动应用,避免动态attach的限制。
  • 容器内执行Arthas时,确认/tmp目录可写,并且PID对得上宿主机还是容器内的Namespace。

5.8 无法访问8563端口

如果Arthas在远程机器上运行,而你想从本地连上去操作,需要确保8563端口(默认telnet端口)和8562端口(默认HTTP端口)被你当前的网络允许连接。一些公司内部网络策略较严格,需要提网络放行申请;或者你可以用ssh隧道把远程端口映射到本地再访问,这样就不用额外开防火墙端口了。

整体来说,Arthas确实是一个让我“用过就回不去”的工具。它解决的不是某一个具体的问题,而是把人从“盲人摸象”的排查模式里解放出来,让你能直接看到运行中JVM的内部。刚开始接触时可能会被它的命令数量吓到,但只要把dashboard、thread、jad、watch、trace、tt这几个核心命令用熟,就已经能覆盖绝大部分日常排查场景了。我自己一次一次踩坑之后才慢慢意识到,满屏的命令并不可怕,关键是把每一条的适用时机记清楚——先看全局,再锁线程,然后用watch或trace深入方法内部,再用ognl处理疑难杂症,这套思路比硬记命令参数重要得多。希望这篇文章能帮你少走一些弯路,也欢迎你把这些命令在测试环境多试几遍,熟能生巧之后,线上问题再也不用靠“重启大法”来解决了。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进