Skip to content

06.3 - 图形化工具(JVisualVM vs Arthas)

定位: 从可视化分析到线上实时诊断——JVisualVM适合"开发环境深挖",Arthas适合"生产环境救命" 面试高频度: ⭐⭐⭐⭐(Arthas近年极高频)

一、为什么需要图形化工具?

命令行工具(jstat/jmap/jstack)能解决大部分问题,但有两个痛点:

  1. 数据不直观jstat的输出需要脑子算,VisualGC能直接看到GC波动曲线
  2. 无法动态干预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 核心功能

功能说明使用场景
OverviewCPU、堆、类、线程概览第一眼诊断
MonitorCPU、堆、类、线程实时折线图看趋势
Threads线程状态时间线看线程生命周期、死锁检测
Sampler采样CPU/内存分析低开销,快速定位热点
Profiler插桩CPU/内存分析精确但开销大
VisualGC堆内存可视化⭐ 最实用——直观看各代使用变化
Heap Dump分析堆dump直接浏览对象树

2.3 JVisualVM实战技巧

VisualGC插件是最有用的功能:

安装: Tools → Plugins → Available Plugins → VisualGC

VisualGC能实时展示:

┌─────────────────────────────────────────────────────┐
│               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:

  1. trace这个接口的方法——直接看到哪一行最耗时
  2. watch方法的参数——看请求数据是否正确
  3. jad反编译——确认部署的确实是正确的代码版本
  4. profiler生成火焰图——发现CPU热点
  5. ————全程没有重启进程,没有重新部署,没有修改一行代码

这就是Arthas的威力。

3.2 安装和启动

bash
# 一键安装
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 — 线程分析

bash
# 查看所有线程
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 — 反编译类

bash
# 反编译整个类
jad com.example.service.OrderService

# 只看源码部分
jad --source-only com.example.service.OrderService

# 反编译具体方法
jad com.example.service.OrderService createOrder

实战场景: 怀疑部署的不是最新代码。用jad直接看JVM中真正加载的字节码,确认版本。特别适合排查"明明改了为什么没生效"的问题。

watch — 方法调用观测

bash
# 观察方法参数和返回值
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 — 方法调用链路(找性能瓶颈)

bash
# 追踪方法内部调用耗时
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 — 查看调用栈

bash
# 查看方法被谁调用了
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) — 时空隧道

bash
# 记录方法的每次调用
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 — 火焰图

bash
# 启动CPU采样(默认5秒)
profiler start

# 等待一段时间...

# 停止采样并生成火焰图
profiler stop --format html

# 输出: 生成火焰图文件 /tmp/arthas-output/20200615-1000.html

实战场景: CPU飙高但不知道哪里热。火焰图的每个"火焰"代表一个调用栈——最宽的火焰就是消耗CPU最多的调用路径

redefine — 热替换类

bash
# 编译修复后的类,然后
redefine /tmp/OrderService.class

⚠️ 生产慎用: redefine不会改变原类的方法签名和结构(不能增删方法/字段)。它只替换方法体。重启后失效。这是"紧急止血"手段,不是常规修复方案。

ognl — 执行表达式

bash
# 获取系统属性
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日志可视化

Knowledge4J — Java 知识库