一、引言
"线上 CPU 突然飙到 100%,你第一反应是什么?"
对于大多数 Java 开发者来说,答案可能是:"登录服务器,启动 Arthas!"
Arthas 确实是 Java 诊断领域的瑞士军刀,但它不是万能的。今天,我要给你介绍几款更专业、更强大的诊断工具,尤其是第 3 个——async-profiler,堪称"核弹级"推荐。
二、诊断工具全景图
在深入每个工具之前,先来看一张全景对比图:
┌─────────────────────────────────────────────────────────────────┐
│ │
│ Java 诊断工具对比 │
│ │
│ ┌──────────────┬──────────┬──────────┬──────────┬───────────┐ │
│ │ 工具 │ 是否需登录│ 性能影响 │ 功能覆盖 │ 学习成本 │ │
│ ├──────────────┼──────────┼──────────┼──────────┼───────────┤ │
│ │ Arthas │ ✅ 需要 │ 低-中 │ 全能 │ 低 │ │
│ ├──────────────┼──────────┼──────────┼──────────┼───────────┤ │
│ │ jconsole │ ❌ 远程 │ 低 │ 基础 │ 极低 │ │
│ ├──────────────┼──────────┼──────────┼──────────┼───────────┤ │
│ │ jvisualvm │ ❌ 远程 │ 低-中 │ 进阶 │ 低 │ │
│ ├──────────────┼──────────┼──────────┼──────────┼───────────┤ │
│ │ async-profiler│ ✅ 需要 │ 极低 │ 性能分析 │ 中 │ │
│ ├──────────────┼──────────┼──────────┼──────────┼───────────┤ │
│ │ BTrace │ ✅ 需要 │ 低 │ 动态追踪 │ 高 │ │
│ ├──────────────┼──────────┼──────────┼──────────┼───────────┤ │
│ │ JFR │ ✅/❌ │ 零-极低 │ 全链路 │ 中 │ │
│ └──────────────┴──────────┴──────────┴──────────┴───────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
三、工具一:Arthas——经典全能选手
3.1 为什么 Arthas 是首选?
Arthas 是阿里巴巴开源的 Java 诊断工具,几乎能解决你遇到的所有问题:
# 常用命令一览
$ arthas-boot
# 1. 查看线程状态
thread -n 3
# 2. 监控方法执行
watch com.example.service.UserService getUser "{params, returnObj}"
# 3. 反编译类
jad com.example.controller.OrderController
# 4. 查看类加载器
classloader -t
# 5. 性能分析
profiler start
profiler status
profiler stop -f flame.html
3.2 Arthas 的局限性
┌─────────────────────────────────────────────────────────────┐
│ │
│ Arthas 的局限性 │
│ │
│ ❌ 需要登录服务器 │
│ - 安全审计要求严格的环境无法使用 │
│ - 需要 sudo 权限 │
│ │
│ ❌ Profiler 准确度一般 │
│ - 使用的是 Java 层采样 │
│ - 对于 native 方法和系统调用不够准确 │
│ │
│ ❌ 对 JVM 有一定侵入性 │
│ - 通过 attach 机制注入 agent │
│ - 极端情况下可能影响应用性能 │
│ │
│ ❌ 无法生成高质量火焰图 │
│ - 生成的火焰图信息不够完整 │
│ - 没有 async-profiler 专业 │
│ │
└─────────────────────────────────────────────────────────────┘
四、工具二:jconsole/jvisualvm——JDK 自带的基础工具
4.1 jconsole——轻量级监控
# 启动方式
$ jconsole
# 连接方式:
# 1. 本地连接(同一台机器)
# 2. 远程连接(需要配置 JMX)
远程 JMX 配置:
java -jar myapp.jar \
-Dcom.sun.management.jmxremote \
-Dcom.sun.management.jmxremote.port=9999 \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote.ssl=false \
-Djava.rmi.server.hostname=192.168.1.100
主要功能:
- 内存使用情况监控
- 线程状态查看
- CPU 使用率图表
- 类加载统计
4.2 jvisualvm——更强大的可视化工具
# 启动方式
$ jvisualvm
# 插件安装:
# 1. 菜单栏 -> 工具 -> 插件
# 2. 可用插件 -> 选择需要的插件 -> 安装
关键插件:
- Visual GC:可视化垃圾回收
- MBeans:查看和操作 MBean
- Sampler:CPU 和内存采样分析
4.3 优缺点总结
优点:
├── 零依赖(JDK 自带)
├── 远程连接无需登录服务器
├── 界面友好,易于上手
└── 基础功能足够日常排查
缺点:
├── 功能有限,深度分析不够
├── 远程连接需要配置 JMX(安全风险)
├── 没有火焰图生成能力
└── 对于复杂问题力不从心
五、工具三:async-profiler——核弹级性能分析神器 ⭐⭐⭐
5.1 为什么说它是"核弹级"?
async-profiler 是一款基于 HotSpot JVM 的高性能采样分析器,特点:
- 四种采样模式:CPU、内存分配、锁、墙钟时间
- 极低性能影响:采样模式下几乎不影响应用性能
- 高精度:基于 native 层采样,准确度远超 Java 层工具
- 火焰图生成:一键生成专业级火焰图
5.2 安装与使用
# 安装
$ git clone https://github.com/jvm-profiling-tools/async-profiler
$ cd async-profiler
$ make
# 基本用法
$ ./profiler.sh -d 30 -f flame.html <pid>
# 查看帮助
$ ./profiler.sh --help
5.3 四种采样模式详解
模式一:CPU 采样(默认)
# CPU 采样 30 秒,生成火焰图
./profiler.sh -d 30 -f cpu-flame.html <pid>
# 带方法过滤的 CPU 采样
./profiler.sh -d 30 -f cpu-filtered.html -i "com.example.**" <pid>
适用场景:CPU 使用率高、响应慢、线程阻塞
模式二:内存分配采样
# 内存分配采样,统计分配热点
./profiler.sh -d 30 -e alloc -f alloc-flame.html <pid>
# 指定采样间隔(每分配 512KB 采样一次)
./profiler.sh -d 30 -e alloc -i 512k -f alloc-flame.html <pid>
适用场景:内存占用高、频繁 GC、OOM 排查
模式三:锁竞争采样
# 锁竞争采样,找出锁等待热点
./profiler.sh -d 30 -e lock -f lock-flame.html <pid>
# 分析特定锁
./profiler.sh -d 30 -e lock -f lock-specific.html --lock com.example.MyLock <pid>
适用场景:锁竞争严重、线程阻塞、响应延迟
模式四:墙钟时间采样
# 墙钟时间采样,包含等待时间(如 IO 等待)
./profiler.sh -d 30 -e wall -f wall-flame.html <pid>
# 分析特定线程
./profiler.sh -d 30 -e wall -f wall-thread.html -t <pid>
适用场景:总响应时间长、IO 密集型应用、慢查询分析
5.4 火焰图解读
火焰图解读技巧:
┌─────────────────────────────────────────────────────────────┐
│ │
│ 火焰图 = 堆栈可视化 │
│ │
│ X 轴:采样次数(宽度 = 时间占比) │
│ Y 轴:调用栈深度(从下往上) │
│ 颜色:随机颜色,无特殊含义 │
│ │
│ 读火焰图: │
│ 1. 找"平顶"——宽度很大的函数 │
│ 2. 看"深度"——调用链越长越值得关注 │
│ 3. 关注"热点路径"——从底部到顶部的完整路径 │
│ │
│ 示例: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ MyService.process() ████████████████████████████ │ │
│ │ └─ UserDAO.query() ████████████████████ │ │
│ │ └─ JDBC.execute() ██████████████ │ │
│ │ └─ MySQL.query() ████████ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 结论:MyService.process() 是热点,大部分时间耗在 DB 查询 │
│ │
└─────────────────────────────────────────────────────────────┘
5.5 与 Arthas Profiler 的对比
┌─────────────────────────────────────────────────────────────┐
│ │
│ async-profiler vs Arthas Profiler │
│ │
│ 对比维度 async-profiler Arthas Profiler │
│ ────────────── ─────────────────── ───────────────── │
│ 采样精度 Native 层采样 Java 层采样 │
│ 准确度 极高 中等 │
│ 性能影响 <1% 5%-10% │
│ 火焰图质量 专业级 基础级 │
│ 支持模式 CPU/alloc/lock/wall CPU │
│ 易用性 中等 低 │
│ 学习成本 中等 低 │
│ │
│ 结论: │
│ ├── 日常快速排查:Arthas 更方便 │
│ ├── 深度性能分析:async-profiler 是首选 │
│ └── 生产环境调优:必须掌握 async-profiler │
│ │
└─────────────────────────────────────────────────────────────┘
六、工具四:BTrace——安全的生产环境动态追踪
6.1 什么是 BTrace?
BTrace 是一个安全的 Java 动态追踪工具,可以在不修改代码、不重启应用的情况下,对生产环境进行实时追踪。
核心特点:
- 安全沙箱:不允许修改程序状态
- 只读追踪:只能观察,不能干预
- 热部署:脚本可以动态加载
- 低侵入性:基于 Java Agent,影响极小
6.2 BTrace 脚本示例
示例 1:监控方法调用耗时
@BTrace
public class MethodDuration {
@TLS
static long startTime;
@OnMethod(clazz = "com.example.service.OrderService",
method = "createOrder")
public static void onEnter() {
startTime = System.currentTimeMillis();
}
@OnMethod(clazz = "com.example.service.OrderService",
method = "createOrder",
location = @Location(Kind.RETURN))
public static void onReturn() {
long duration = System.currentTimeMillis() - startTime;
if (duration > 1000) {
println("Slow method: createOrder, duration: " + duration + "ms");
jstack();
}
}
}
示例 2:监控异常抛出
@BTrace
public class ExceptionMonitor {
@OnMethod(clazz = "java.lang.Throwable",
method = "<init>")
public static void onException(Throwable th) {
println("Exception: " + th.getClass().getName());
println("Message: " + th.getMessage());
jstack();
}
}
6.3 运行 BTrace
# 编译脚本
btracec MethodDuration.java
# 运行追踪
btrace <pid> MethodDuration.java
# 查看已加载的脚本
btrace -l <pid>
# 卸载脚本
btrace -u <pid> <script-id>
6.4 适用场景
适用场景:
├── 监控方法调用耗时
├── 追踪异常抛出
├── 统计方法调用次数
├── 监控参数和返回值
├── 分析线程等待时间
不适用场景:
├── 修改程序状态
├── 性能分析(不如 async-profiler)
├── 内存分析
七、工具五:JFR——零性能损耗的生产级事件记录
7.1 什么是 JFR?
JDK Flight Recorder(JFR) 是 Java 内置的事件记录系统,从 Java 11 开始开源,具有以下特点:
- 零性能损耗:默认开启的情况下性能影响 <1%
- 全链路追踪:从 JVM 到应用层的完整事件记录
- 生产级安全:专为生产环境设计
- 丰富的事件类型:GC、线程、锁、IO、网络等
7.2 三种启动方式
方式一:启动时开启
java -jar myapp.jar \
-XX:StartFlightRecording=duration=60s,filename=profile.jfr
方式二:运行时开启(使用 jcmd)
# 查看 JVM 进程
jps
# 开始记录(60 秒)
jcmd <pid> JFR.start duration=60s filename=profile.jfr
# 查看记录状态
jcmd <pid> JFR.check
# 停止记录
jcmd <pid> JFR.stop
方式三:环境变量开启
export JAVA_TOOL_OPTIONS="-XX:+FlightRecorder"
java -jar myapp.jar
7.3 JFR 事件类型
JFR 事件类型:
┌─────────────────────────────────────────────────────────────┐
│ │
│ 1. GC 事件 │
│ ├── GC Pause │
│ ├── GC Phase Summary │
│ ├── Heap Statistics │
│ └── Allocation Failure │
│ │
│ 2. 线程事件 │
│ ├── Thread Start/Stop │
│ ├── Thread Dump │
│ ├── Lock Contention │
│ └── Thread Park │
│ │
│ 3. IO 事件 │
│ ├── File Read/Write │
│ ├── Socket Read/Write │
│ └── Process IO │
│ │
│ 4. 应用事件 │
│ ├── Method Profiling │
│ ├── Exception Thrown │
│ ├── Timer Event │
│ └── Custom Event │
│ │
└─────────────────────────────────────────────────────────────┘
7.4 使用 JMC(JDK Mission Control)分析
# 启动 JMC
$ jmc
# 打开 JFR 文件:
# 1. 文件 -> 打开 -> 选择 .jfr 文件
# 2. 查看各维度数据
JMC 分析维度:
- 概览:CPU、内存、GC、线程等核心指标
- 内存:堆使用、分配速率、GC 暂停
- 代码:方法热点、调用树、异常统计
- 线程:线程状态、锁竞争、死锁检测
- IO:文件、网络、进程 IO
7.5 自定义 JFR 事件
@Name("com.example.OrderCreateEvent")
@Label("Order Create Event")
@Category("Business")
public class OrderCreateEvent extends Event {
@Label("Order ID")
public long orderId;
@Label("User ID")
public long userId;
@Label("Amount")
public double amount;
@Label("Duration")
public long duration;
}
// 使用自定义事件
public class OrderService {
public Order createOrder(OrderRequest request) {
OrderCreateEvent event = new OrderCreateEvent();
event.orderId = generateOrderId();
event.userId = request.getUserId();
event.amount = request.getAmount();
event.begin();
try {
// 业务逻辑
return orderRepository.save(order);
} finally {
event.duration = System.currentTimeMillis() - event.getStartTime();
event.commit();
}
}
}
八、实战:CPU 飙高问题排查流程
8.1 完整排查流程
CPU 飙高排查流程:
┌─────────────────────────────────────────────────────────────┐
│ │
│ 1. 定位问题进程 │
│ jps / top │
│ │
│ 2. 初步分析(Arthas) │
│ arthas-boot -> thread -n 3 │
│ 快速定位热点线程 │
│ │
│ 3. 深度分析(async-profiler) │
│ ./profiler.sh -d 30 -f cpu-flame.html <pid> │
│ 生成火焰图,精准定位热点函数 │
│ │
│ 4. 全链路分析(JFR) │
│ jcmd <pid> JFR.start duration=60s filename=profile.jfr │
│ 分析 GC、锁、IO 等综合因素 │
│ │
│ 5. 动态追踪(BTrace) │
│ 监控热点方法的参数、返回值、耗时 │
│ │
│ 6. 验证修复 │
│ 重新运行 async-profiler 确认优化效果 │
│ │
└─────────────────────────────────────────────────────────────┘
8.2 实战案例
场景:线上订单服务 CPU 突然飙到 100%
# 步骤 1:定位进程
$ jps
12345 OrderService
# 步骤 2:Arthas 初步分析
$ ./as.sh 12345
thread -n 3
# 发现大量线程卡在 OrderService.calculate()
# 步骤 3:async-profiler 深度分析
$ ./profiler.sh -d 30 -f cpu-flame.html 12345
# 火焰图显示 calculate() 中 BigDecimal 运算占用 80% CPU
# 步骤 4:JFR 全链路分析
$ jcmd 12345 JFR.start duration=60s filename=profile.jfr
# JMC 分析发现频繁的 BigDecimal 运算导致大量对象分配
# 步骤 5:修复
# 将 BigDecimal 运算改为使用 primitive 类型
# 添加缓存避免重复计算
# 步骤 6:验证
$ ./profiler.sh -d 30 -f cpu-flame-after.html 12345
# CPU 使用率从 100% 降至 20%
九、工具选型指南
9.1 按场景选择
┌─────────────────────────────────────────────────────────────┐
│ │
│ 场景 推荐工具 理由 │
│ ───────────────── ────────────────── ────────────── │
│ 日常快速排查 Arthas 全能、易用 │
│ 远程监控 jconsole/jvisualvm 无需登录 │
│ CPU/内存性能分析 async-profiler 高精度、火焰图 │
│ 动态追踪 BTrace 安全、只读 │
│ 全链路分析 JFR 零损耗、生产级 │
│ 复杂问题定位 JFR + async-profiler 组合拳 │
│ │
└─────────────────────────────────────────────────────────────┘
9.2 学习建议
学习路径:
┌─────────────────────────────────────────────────────────────┐
│ │
│ 入门阶段(1-2 周) │
│ ├── 掌握 Arthas 常用命令 │
│ └── 熟悉 jconsole/jvisualvm 基础功能 │
│ │
│ 进阶阶段(2-4 周) │
│ ├── 掌握 async-profiler 四种采样模式 │
│ ├── 学会解读火焰图 │
│ └── 掌握 JFR 基本使用 │
│ │
│ 高级阶段(持续学习) │
│ ├── 掌握 BTrace 脚本编写 │
│ ├── 学会自定义 JFR 事件 │
│ └── 建立系统化的问题排查方法论 │
│ │
└─────────────────────────────────────────────────────────────┘
十、完整示例工程
我为你准备了一个专门用于练习这些工具的示例工程:
10.1 项目结构
java-diagnostic-tools-demo/
├── src/main/java/com/example/demo/
│ ├── DemoApplication.java
│ └── controller/
│ └── DiagnosticController.java
├── src/main/resources/
│ └── application.yml
├── scripts/
│ ├── arthas-commands.txt
│ ├── async-profiler-cpu.sh
│ ├── async-profiler-alloc.sh
│ ├── async-profiler-lock.sh
│ ├── jfr-record.sh
│ └── btrace-script.java
└── pom.xml
10.2 模拟问题端点
@RestController
@RequestMapping("/api")
public class DiagnosticController {
// CPU 热点:大量计算
@GetMapping("/cpu-hotspot")
public String cpuHotspot(@RequestParam int iterations) {
double result = 0;
for (int i = 0; i < iterations; i++) {
result += Math.sin(i) * Math.cos(i) * Math.tan(i);
}
return "Result: " + result;
}
// 内存分配:创建大量对象
@GetMapping("/memory-burst")
public String memoryBurst(@RequestParam int count) {
List<String> list = new ArrayList<>();
for (int i = 0; i < count; i++) {
list.add(UUID.randomUUID().toString());
}
return "Created " + count + " objects";
}
// 锁竞争:模拟并发锁等待
@GetMapping("/lock-contention")
public String lockContention() throws InterruptedException {
synchronized (this) {
Thread.sleep(100);
}
return "Done";
}
}
10.3 使用脚本
# 启动应用
mvn spring-boot:run
# 模拟 CPU 热点
curl "http://localhost:8080/api/cpu-hotspot?iterations=10000000"
# 模拟内存分配
curl "http://localhost:8080/api/memory-burst?count=100000"
# 模拟锁竞争(并发请求)
for i in {1..100}; do
curl "http://localhost:8080/api/lock-contention" &
done
# async-profiler CPU 分析
./scripts/async-profiler-cpu.sh <pid>
# JFR 记录
./scripts/jfr-record.sh <pid>
十一、总结
11.1 工具对比总结
┌──────────────┬─────────────────────────────────────────────┐
│ 工具 │ 核心优势 │
├──────────────┼─────────────────────────────────────────────┤
│ Arthas │ 全能、易用、快速排查 │
├──────────────┼─────────────────────────────────────────────┤
│ jconsole │ 零依赖、远程监控、基础功能 │
├──────────────┼─────────────────────────────────────────────┤
│ jvisualvm │ 可视化、插件丰富、进阶监控 │
├──────────────┼─────────────────────────────────────────────┤
│ async-profiler│ 高精度、火焰图、四种采样模式(核弹级推荐) │
├──────────────┼─────────────────────────────────────────────┤
│ BTrace │ 安全、只读、动态追踪 │
├──────────────┼─────────────────────────────────────────────┤
│ JFR │ 零损耗、生产级、全链路事件记录 │
└──────────────┴─────────────────────────────────────────────┘
11.2 核心要点
- Arthas 是基础:日常排查的首选工具
- async-profiler 是进阶:深度性能分析的必备工具
- JFR 是生产级:零损耗的全链路监控
- 工具组合使用:没有最好的工具,只有最适合的组合
💡 互动话题:你在生产环境中最常用的诊断工具是什么?遇到过哪些棘手的问题?欢迎在评论区分享你的经验!
