06.3 - 图形化工具(JVisualVM vs Arthas)
定位: 从可视化分析到线上实时诊断——JVisualVM适合"开发环境深挖",Arthas适合"生产环境救命" 面试高频度: ⭐⭐⭐⭐(Arthas近年极高频)
一、为什么需要图形化工具?
命令行工具(jstat/jmap/jstack)能解决大部分问题,但有两个痛点:
- 数据不直观 —
jstat的输出需要脑子算,VisualGC能直接看到GC波动曲线 - 无法动态干预 —
jstack只能拍快照,而Arthas能实时watch方法调用、redefine类、生成火焰图
┌─────────────────────────────────────────────────────────────────────┐
│ 工具选择决策树 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 你有桌面GUI环境吗? │
│ ├── 有 → 用JVisualVM(直觉型分析:GC曲线、线程图、Sampler) │
│ └── 没有(生产服务器SSH) │
│ ├── 只是看一眼状态 → jstat/jstack │
│ ├── 需要深度分析 → Arthas │
│ │ ├── 看方法参数/返回值 → watch │
│ │ ├── 找性能热点 → profiler 火焰图 │
│ │ ├── 怀疑加载了错的类 → jad │
│ │ ├── 想知道谁调了方法 → stack │
│ │ └── 想重放请求 → tt │
│ └── 要分析堆dump → 下载到本地用MAT │
│ │
└─────────────────────────────────────────────────────────────────────┘二、JVisualVM — 可视化分析
2.1 是什么?
JDK自带的图形化监控/分析工具($JAVA_HOME/bin/jvisualvm)。JDK 9+不再打包,需单独下载:VisualVM官网。
2.2 核心功能
| 功能 | 说明 | 使用场景 |
|---|---|---|
| Overview | CPU、堆、类、线程概览 | 第一眼诊断 |
| Monitor | CPU、堆、类、线程实时折线图 | 看趋势 |
| Threads | 线程状态时间线 | 看线程生命周期、死锁检测 |
| Sampler | 采样CPU/内存分析 | 低开销,快速定位热点 |
| Profiler | 插桩CPU/内存分析 | 精确但开销大 |
| VisualGC | 堆内存可视化 | ⭐ 最实用——直观看各代使用变化 |
| Heap Dump | 分析堆dump | 直接浏览对象树 |
2.3 JVisualVM实战技巧
VisualGC插件是最有用的功能:
安装: Tools → Plugins → Available Plugins → VisualGCVisualGC能实时展示:
┌─────────────────────────────────────────────────────┐
│ Metaspace [==== ]│ 60%
├─────────────────────────────────────────────────────┤
│ Old Gen [========]│ 85% ← 关注
├─────────────────────────────────────────────────────┤
│ Eden [==== ]│ 45%
├─────────────────────────────────────────────────────┤
│ S0: [ ] S1: [=======] (活跃对象在S1) │
└─────────────────────────────────────────────────────┘
GC Activity: ▁▃▆█▅▃▁▁▃▆█▅▃▁ (柱状图)实时看到Eden满了 → Minor GC → 对象移到Survivor → Survior放不下 → 晋升Old。相比命令行,一眼就能看到GC模式。
2.4 JVisualVM的局限性
| 局限性 | 说明 | 替代方案 |
|---|---|---|
| 需要GUI | 桌面环境才能运行 | Arthas(命令行) |
| 远程连接配置繁琐 | 需要JMX端口暴露 | jstatd + jmx exporter |
| 无法动态修改运行时 | 只能看不能动 | jinfo + Arthas redefine |
| 堆dump分析较慢 | 大堆文件会卡 | MAT(Eclipse Memory Analyzer) |
三、Arthas(阿尔萨斯)
3.1 是什么?为什么革命性?
Arthas是Alibaba开源的Java诊断工具。它的核心价值是: 在不重启、不修改代码、无侵入的情况下,对生产环境的Java应用进行实时诊断。
想象一下:你收到告警说线上某个接口很慢。你用Arthas:
trace这个接口的方法——直接看到哪一行最耗时watch方法的参数——看请求数据是否正确jad反编译——确认部署的确实是正确的代码版本profiler生成火焰图——发现CPU热点- ————全程没有重启进程,没有重新部署,没有修改一行代码
这就是Arthas的威力。
3.2 安装和启动
# 一键安装
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar
# 或者用as.sh
curl -L https://arthas.aliyun.com/install.sh | sh启动后选择要attach的Java进程(输入编号),进入交互式命令行。
3.3 Arthas命令详解
dashboard — 系统总览面板
$ dashboard
ID NAME GROUP PRIORITY STATE %CPU TIME INTERRUPTED DAEMON
25 Attach Listener system 9 RUNNABLE 0.0 0:0 false true
16 arthas-NettyHttpTelnet system 5 RUNNABLE 0.1 0.0 false true
...
Memory used total max usage GC
heap 512M 1024M 2048M 50.00% gc.young.count: 1234
g1_eden_space 128M 256M - 50.00% gc.young.mean: 12ms
g1_survivor_space 16M 16M - 100.0% gc.old.count: 5
g1_old_gen 368M 752M 2048M 48.94% gc.old.mean: 350ms
Runtime Information
OS-Mac OS X 10.15.7
JVM-Oracle Corporation Java HotSpot(TM) 64-Bit Server VM (25.271-b09)
CPU COUNT-8, LOAD-0.5, 1-MIN LOAD-0.8相当于 htop for JVM。一眼看到CPU、内存、GC、线程状态。
thread — 线程分析
# 查看所有线程
thread
# 查看Top N CPU消耗线程
thread -n 3
# ─────────────────
# 输出:
# "http-nio-8080-exec-18" Id=85 cpuUsage=45% ⬅️ CPU最高的线程
# at com.example.service.OrderService.createOrder(OrderService.java:125)
# at com.example.controller.OrderController.create(OrderController.java:45)
# ...
# 查看线程栈(类似jstack但更清晰)
thread 85
# 查找死锁(比jstack -l更直观)
thread -b
# ─────────────────
# 输出:
# "http-nio-8080-exec-18" Id=85 BLOCKED
# at com.example.service.OrderService.createOrder(OrderService.java:125)
# - waiting to lock <0x000000076b5c6a60>
# "http-nio-8080-exec-17" Id=84 BLOCKED
# at com.example.service.InventoryService.deduct(InventoryService.java:78)
# - waiting to lock <0x000000076b5c6a58>
# Found 1 deadlock.
thread -n 3比传统的top -H -p + jstack快一个数量级。
jad — 反编译类
# 反编译整个类
jad com.example.service.OrderService
# 只看源码部分
jad --source-only com.example.service.OrderService
# 反编译具体方法
jad com.example.service.OrderService createOrder实战场景: 怀疑部署的不是最新代码。用
jad直接看JVM中真正加载的字节码,确认版本。特别适合排查"明明改了为什么没生效"的问题。
watch — 方法调用观测
# 观察方法参数和返回值
watch com.example.service.OrderService createOrder '{params, returnObj, throwExp}' -x 2
# 输出示例(每次调用该方法时打印):
# ─────────────────
# method=com.example.service.OrderService.createOrder
# params=[
# OrderDTO(id=1001, userId=42, amount=99.9)
# ]
# returnObj=OrderVO(orderId=1001, status="SUCCESS")
# cost=35ms参数说明:
| 参数 | 含义 | 说明 |
|---|---|---|
-x | 展开深度 | 控制对象字段展开层数 |
-n | 执行次数 | 观测N次后自动退出 |
-f | 过滤条件 | '{params[0].amount > 100}' 只观测大额订单 |
-e | 异常时触发 | 只在抛出异常时记录 |
实战场景: 线上某个接口报错,但日志不够详细。
watch能帮你看到实际传入的参数和返回的值——不需要加日志重新部署。
trace — 方法调用链路(找性能瓶颈)
# 追踪方法内部调用耗时
trace com.example.service.OrderService createOrder
# 输出示例:
# ─────────────────
# `---[0.345ms] com.example.service.OrderService:createOrder()
# +---[0.002ms] com.example.service.OrderService:validateOrder()
# +---[0.100ms] com.example.service.UserService:getUser()
# +---[0.200ms] com.example.service.InventoryService:deduct() ← 耗时最长
# `---[0.043ms] com.example.dao.OrderDao:insert()
# 跳过JDK类,只看业务代码
trace --skipJDKMethod false com.example.service.OrderService createOrder实战场景: 接口响应慢。
trace能告诉你哪个子调用最慢——是数据库查询慢?远程调用慢?还是本地的计算慢?
stack — 查看调用栈
# 查看方法被谁调用了
stack com.example.service.InventoryService deduct
# 输出示例:
# ─────────────────
# `---[0.2ms] com.example.service.InventoryService:deduct()
# +---[0.2ms] com.example.service.OrderService:createOrder()
# +---[0.2ms] com.example.controller.OrderController:create()实战场景: 你发现
deduct()的调用次数异常高。用stack看是谁在调用它——可能是某个定时任务在反复调用。
tt (Time Tunnel) — 时空隧道
# 记录方法的每次调用
tt -t com.example.service.OrderService createOrder
# 输出示例(每行一个调用记录):
# INDEX TIMESTAMP COST(ms) IS-RET IS-EXP OBJECT METHOD
# 1000 2026-06-15 10:00:00.123 35 true false 0x6c5b6a60 createOrder
# 1001 2026-06-15 10:00:01.456 42 true false 0x6c5b6a60 createOrder
# 1002 2026-06-15 10:00:02.789 2000 false true 0x6c5b6a60 createOrder ← 超时异常
# 重放第1002次调用(重现bug!)
tt -i 1002 -p实战场景: 某个请求偶尔超时。用
tt记录一段时间,然后重放那个异常的请求——复现bug比修复bug更难,tt解决了"复现"这个难题。
profiler — 火焰图
# 启动CPU采样(默认5秒)
profiler start
# 等待一段时间...
# 停止采样并生成火焰图
profiler stop --format html
# 输出: 生成火焰图文件 /tmp/arthas-output/20200615-1000.html实战场景: CPU飙高但不知道哪里热。火焰图的每个"火焰"代表一个调用栈——最宽的火焰就是消耗CPU最多的调用路径。
redefine — 热替换类
# 编译修复后的类,然后
redefine /tmp/OrderService.class⚠️ 生产慎用:
redefine不会改变原类的方法签名和结构(不能增删方法/字段)。它只替换方法体。重启后失效。这是"紧急止血"手段,不是常规修复方案。
ognl — 执行表达式
# 获取系统属性
ognl '@java.lang.System@getProperty("java.version")'
# 获取静态字段
ognl '@com.example.cache.OrderCache@cache.size()'
# 调用Spring Bean方法
ognl '@com.example.SpringContext@getBean("orderService").getOrderCount()'实战场景: 你想知道某个缓存当前的大小,但JMX没有暴露。
ognl可以直接调用代码——不需要加接口重新部署。
四、Arthas命令选择流程
┌──────────────────────────┐
│ attach到目标Java进程 │
└────────────┬─────────────┘
│
┌────────────▼─────────────┐
│ dashboard 看全局状态 │
└────────────┬─────────────┘
│
┌──────────────────┼──────────────────┐
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ 接口慢/delay │ │ CPU高 │ │ 逻辑/数据问题 │
└───────┬───────┘ └───────┬───────┘ └───────┬───────┘
│ │ │
┌───────▼───────┐ ┌───────▼───────┐ ┌───────▼───────┐
│ trace 追踪 │ │ thread -n 3 │ │ watch 参数 │
│ 找出慢的调用 │ │ 看最耗CPU线程 │ │ 检查输入输出 │
└───────┬───────┘ └───────┬───────┘ └───────┬───────┘
│ │ │
┌───────▼───────┐ ┌───────▼───────┐ ┌───────▼───────┐
│ profiler │ │ stack 深入 │ │ tt 记录重放 │
│ 火焰图确认 │ │ + profiler │ │ 复现问题 │
└───────────────┘ └───────────────┘ └───────────────┘
│
┌────────────▼─────────────┐
│ jad 确认代码版本 │
│ redefine 紧急修复(慎用) │
└─────────────────────────┘五、面试视角
高频面试题
| 面试题 | 考察点 | 回答要点 |
|---|---|---|
| "Arthas能做什么?" | 工具认知 | 实时诊断:dashboard看状态、trace找热点、watch看参数、jad反编译、profiler火焰图 |
| "trace和watch的区别?" | 理解深度 | trace = 方法调用链路上每个子调用的耗时;watch = 单个方法的参数/返回值/异常 |
| "jad的原理是什么?" | 底层理解 | 从JVM已加载的类中反编译字节码,不是读源文件 |
| "Arthas有侵入性吗?" | 生产安全 | Attach模式,不需要重启,不修改类(redefine除外),对原进程影响极小 |
| "JVisualVM和Arthas怎么选?" | 场景判断 | JVisualVM桌面GUI开发环境用;Arthas命令行生产环境用 |
| "tt -i 1002 -p的作用是什么?" | 高级用法 | 重放第1002次记录的调用,重现问题 |
| "ognl能做什么?" | 扩展能力 | 运行时执行表达式,调用方法,查看字段,无需重启 |
面试回答模板
Q: 遇到过什么用Arthas解决的问题? A: 一次线上某一个接口偶尔超时,日志无法复现。我用
trace追踪该接口——每次请求耗时都在100ms以内,但有一次突然2000ms。用tt记录所有调用,重放那次异常请求后发现是Redis连接池在等待连接。最终定位是某次Redis主从切换后连接池没有重建。整个过程没有重启服务,没有添加日志。
Q: Arthas的watch和trace有什么区别? A: 核心区别在于作用范围不同。
watch关注单个方法的入参、返回值、异常;trace关注方法内部调用链路的耗时分布。watch适合排查"数据对不对",trace适合排查"哪里慢"。
📚 相关链接
- ← 返回 **调优索引**
- **常用工具(jps与jstat与jmap与jstack与jinfo)** - 命令行工具基础
- **OOM排查方法论** - 堆dump分析(MAT)
- **CPU飙升排查方法论** - 火焰图分析
- **GC日志分析** - GC日志可视化