大家想象这样一个场景:
上周二凌晨两点十七分,你的手机像被诅咒了一样开始狂震。
运维在群里@了你八次,最后一条是:“王哥,支付接口全挂了,用户付不了钱,客服电话已经炸了,你赶紧看看!”
你迷迷糊糊摸到电脑,远程连上服务器,tail -f 看了半天日志,满屏的红色报错里就仨字:NullPointerException。
没了。
哪个对象是null?谁调用了谁?日志里一句多余的话都没有。你翻了一百多行,发现报错在OrderService的pay方法,但具体哪一行?不知道。参数是什么?不知道。用户当时操作了什么?不知道。
你盯着屏幕,感觉自己像个侦探,面对一堆碎片,却拼不出凶手的脸。
一个小时后,运维问你:“怎么样?定位到了吗?”
你说:“快了快了……”
其实你心里在骂:快个屁,我连门都没摸着。
后来一个老朋友甩给你一句:“你用Arthas啊,watch一下那个方法,参数返回值全能看到,比你看日志强一百倍。”
你抱着试试看的心态装上,一条watch命令下去,三秒后我就看到了那个null参数的来源——前端传过来的userId是空的,而代码里直接用了userId.toString(),没判空。
改完代码,重启,三分钟搞定。
你对着屏幕愣了半天,然后默默把Arthas加入了“保命工具箱”的第一位。
今天我就把这套“救急三板斧”拆开了揉碎了讲。你不用记复杂配置,不用改代码重启,一条命令就能把线上跑着的Java程序扒个底朝天。
一、Arthas是什么?就是Java程序的“照妖镜”
你可以把Arthas想象成给Java进程做“无痛胃镜”的工具。不用开刀(不用重启),不用麻醉(零侵入),伸进去看一眼,里面啥情况一目了然。
以前查线上问题,你得:
- 翻日志(有时日志压根没打)
- 看监控(只能看宏观指标)
- 猜代码(本地和线上可能不一致)
- 改代码加日志,重新发版,等半天
这套流程下来,天都亮了。
而Arthas直接附着在运行中的JVM上,让你能:
- 实时看线程在干啥
- 反编译线上类确认代码版本
- 监控方法的入参、返回值、异常
- 追踪调用链路耗时
- 导出内存快照分析泄漏
关键是——一行代码都不用改,不用重启服务。生产环境最怕重启,Arthas完美绕过了这个痛点。
二、5分钟安装,比点外卖还快
不管你是Windows、Linux还是Mac,三步搞定。
第一步:确认Java环境
java -version
有输出版本号就行,没有就去装。
第二步:下载Arthas启动包
Linux/Mac:
curl -O https://arthas.aliyun.com/arthas-boot.jar
# 或者 wget https://arthas.aliyun.com/arthas-boot.jar
Windows(CMD或PowerShell):
powershell -Command "(New-Object System.Net.WebClient).DownloadFile('https://arthas.aliyun.com/arthas-boot.jar', 'arthas-boot.jar')"
第三步:启动并附着到目标进程
java -jar arthas-boot.jar
它会列出所有Java进程,输入编号,回车,看到[arthas@pid]$就成功了。
如果端口被占,指定其他端口:
java -jar arthas-boot.jar --telnet-port 8564 --http-port 8565
三、六个救命命令,搞定90%的生产事故
命令1:dashboard——给系统做个体检
直接输入dashboard,会看到一个实时刷新的面板:
ID NAME GROUP STATE %CPU
12 http-nio-8080-exec-1 main RUNNABLE 92.3
3 Finalizer system WAITING 0.0
重点关注:
- %CPU列:哪个线程占用高,它就是“嫌疑人”
- MEMORY:内存使用率、GC次数
- STATE:有没有BLOCKED状态的线程(死锁嫌疑)
按Ctrl+C退出。
实战:CPU飙高怎么办?
假设dashboard显示http-nio-8080-exec-5这个线程CPU占用85%,接下来用thread命令扒它。
命令2:thread——把线程的底裤都扒出来
看指定线程的堆栈:
thread 12 # 12是线程ID
输出会显示这个线程正在执行的代码调用链,精确到类名、方法名、行号。
比如你看到:
at com.example.service.PaymentService.calculateFee(PaymentService.java:42)
去查第42行,发现是个循环计算,循环次数没控制好,导致CPU飙高。
检测死锁:
thread -d
如果有死锁,会清楚列出哪个线程持有哪个锁、在等哪个锁,甚至指出代码行号。
看线程状态分布:
thread -stats
如果BLOCKED > 0,说明有锁竞争问题。
命令3:jad——抓出“本地和线上代码不一致”的幽灵
你有没有遇到过:本地跑得好好的,上线就报错。怀疑是代码没发版?直接反编译线上类看看。
jad com.example.service.PaymentService
输出反编译后的完整源码。
还可以只看某个方法:
jad -m calculateFee com.example.service.PaymentService
甚至保存到文件:
jad com.example.service.PaymentService > PaymentService.java
我遇到过最离谱的一次:本地代码里有个null判断,线上反编译出来竟然没有——原因是同事发版的时候传了旧包。有了jad,三秒钟破案。
命令4:watch——给方法装个“行车记录仪”
这是我最常用的命令,相当于在方法入口和出口装了个摄像头,把参数、返回值、异常全拍下来。
监控参数和返回值:
watch com.example.service.PaymentService pay "{params, returnObj}" -x 2
当有人调用pay方法时,会输出:
ts=2024-05-20 15:30:00; [cost=5ms]
result=@ArrayList[
@Object[][
@Long[12345], // 参数1:userId
@BigDecimal[99.8], // 参数2:amount
],
@Boolean[true], // 返回值:true
]
监控异常:
watch com.example.service.PaymentService pay "{params, throwExp}" -x 2
如果抛异常,会看到异常类型和消息,比如:
throwExp=@NullPointerException[
message=null,
stackTrace=[...]
]
按条件过滤:只想看特定参数的调用:
watch com.example.service.PaymentService pay "{params, returnObj}" -x 2 "params[0]==12345"
只想看耗时超过100ms的调用:
watch com.example.service.PaymentService pay "{params, returnObj, cost}" -x 2 "cost>100"
⚠️ 注意:watch会拦截方法调用,高频方法不要长时间监控,用完即关。
命令5:trace——找出“谁拖慢了后腿”
一个方法慢,但不知道是内部哪个子方法慢。用trace追踪调用链路。
trace com.example.service.OrderService createOrder
输出:
ts=2024-05-20 16:00:00; [cost=500ms] OrderService.createOrder()
`---[450ms] StockService.checkStock() # 占了90%时间
`---[448ms] StockMapper.selectStock() # 慢在SQL查询
`---[30ms] PriceService.calculatePrice()
`---[20ms] OrderMapper.insertOrder()
一眼看出是selectStock这个SQL慢,赶紧去优化索引。
只看耗时超过100ms的子调用:
trace com.example.service.OrderService createOrder --cost 100
命令6:heapdump——拍个内存“CT”排查泄漏
怀疑内存泄漏?导出堆快照分析。
heapdump /tmp/heap.hprof
然后下载到本地,用MAT或JProfiler打开,分析哪些对象占内存最大、为什么没释放。
注意:导出会消耗资源,选业务低峰期,确保磁盘空间足够。
四、进阶小技巧:让Arthas更好用
用Web控制台
启动后访问http://服务器IP:8565,图形化界面,命令自动补全,结果更直观。
自定义别名
把长命令缩短:
alias watchPay='watch com.example.service.PaymentService pay "{params,returnObj}" -x 2'
之后直接输watchPay。
OGNL表达式
更灵活的过滤:
watch com.example.service.UserService update "{params[0].age, returnObj}" -x 2 "params[0].age > 30"
五、避坑指南(都是血泪教训)
- 别在高频方法上长期watch:性能损耗虽小,但每秒几万次调用还是会有影响。定位完问题立刻Ctrl+C关掉。
- heapdump要选对时机:别在业务高峰期执行,文件可能几GB,确保磁盘有空间。
- 权限问题:启动Arthas的用户要和Java进程的用户一致,否则连不上。
- Arthas是诊断工具,不是监控工具:用完就退,别常驻。监控用Prometheus+Grafana。
六、来个完整实战:深夜救火全过程
场景:支付接口偶发超时,日志只报“调用第三方超时”,没有更多信息。
步骤1:dashboard看看整体情况,发现支付线程偶尔CPU飙升。
步骤2:thread找高CPU线程,堆栈显示卡在HttpClient.execute。
步骤3:watch监控PaymentService.doPay方法的参数和返回值:
watch com.example.service.PaymentService doPay "{params, returnObj, cost}" -x 2 "cost>1000"
等了一会儿,抓到一条耗时3000ms的调用,发现参数里有个timeout=500,而正常应该是5000——原来是前端传了个错误值。
步骤4:jad反编译前端调用的接口类,确认参数映射是否正确,发现前端字段名写错了,传了一个默认值。
步骤5:通知前端修正字段名,重新发版。问题解决,全程不到半小时。
如果没有Arthas,你可能要翻半天日志、猜参数、查代码,至少两小时。
七、总结
Arthas不是银弹,但它确实能把“半夜爬起来查问题”的恐惧感降低一半。以前我遇到线上问题先慌,现在我先掏出Arthas,三分钟定位不了再说。
它就像一个随身带着的“解码器”,把JVM里那些黑盒信息一条条翻译给你看。
下次你再遇到诡异bug,不妨试试:curl -O https://arthas.aliyun.com/arthas-boot.jar && java -jar arthas-boot.jar
然后一条watch下去,问题可能就照妖镜面前现原形了。
毕竟,程序员的命也是命,能少熬一次夜,就多喝一杯咖啡。

198

被折叠的 条评论
为什么被折叠?



