📌 Profiling工具

1. Profiling概述

1.1 什么是Profiling?

Profiling(性能剖析)是将性能分析下沉到代码级别的技术。如果说监控告诉你"系统哪个接口慢",APM告诉你"这个接口的哪个环节慢",那么Profiling告诉你"哪行代码、哪个方法消耗了最多CPU/内存"。它是性能调优的最终武器——没有Profiling,性能优化就是"盲人摸象"。

1.2 Profiling vs 监控 vs APM

维度监控 (Monitoring)APM (Tracing)Profiling
分析粒度 系统/服务级 接口/调用链级 方法/代码行级
回答的问题 "系统健康吗?" "哪个环节慢了?" "为什么这个环节慢?"
典型输出 CPU%、QPS、错误率 调用链拓扑、span耗时 火焰图、方法耗时排名、GC日志
性能开销 极低 (<1%) 低 (1-5%) 中到高 (5-20%,取决于采样率)
使用频率 7×24持续运行 7×24持续运行 按需开启(问题排查/压测期间)

1.3 Profiling的核心维度

  • CPU Profiling:分析方法/函数的CPU消耗占比,定位热点代码。回答"CPU时间都花在哪了?"
  • Memory Profiling:分析堆内存分配和GC行为,定位内存泄漏和高频分配点。回答"内存都用在哪些对象上了?"
  • Thread Profiling:分析线程状态和锁竞争,定位死锁和线程饥饿。回答"为什么线程都在等待?"
  • IO Profiling:分析磁盘/网络IO的耗时和频率,定位IO瓶颈。回答"为什么IO操作这么慢?"
  • Allocation Profiling:分析对象分配的热点路径,识别频繁创建导致GC压力的代码。回答"哪里在疯狂创建临时对象?"

2. Java Profiling工具对比

工具类型优势劣势推荐场景
Arthas 在线诊断(命令行) 无需重启、无需预先配置、功能全面(watch/trace/monitor/thread等)、生产环境安全 命令行交互,无可视化GUI;新手上手有一定门槛 生产环境在线排查、紧急问题诊断
JProfiler 离线/在线(GUI桌面) 极致可视化(火焰图/调用树/内存快照)、功能最全面、支持远程连接 商业软件(需付费)、Agent体积较大、需在目标JVM启动时挂载 本地/测试环境深度剖析、复杂问题分析
async-profiler 采样器(命令行) 极低开销(<1%)、基于AsyncGetCallTrace+perf_events、支持生成火焰图、开源免费 输出原始数据,需配合FlameGraph工具生成图;无GUI;功能相对单一 生产环境CPU Profiling首选、容器化环境
VisualVM 在线/离线(GUI桌面) JDK自带、免费、入门简单、支持插件扩展 功能深度不足、远程Profiling需JMX暴露、生产环境性能开销较大 本地开发调试、学习Profiling入门
jstack/jmap/jstat 命令行工具(JDK内置) 零依赖、JDK自带、快速获取线程/内存/GC快照 功能分散、输出需人工解读、无历史趋势 快速排查线程死锁、内存泄漏初步诊断

3. Arthas 实战

3.1 Arthas核心命令速查

Arthas(阿尔萨斯)是阿里巴巴开源的Java诊断工具,被誉为"Java界的瑞士军刀"。它的最大特点是不需要修改任何代码,不需要重启应用,即可动态挂载到运行中的JVM上进行诊断。

命令功能典型用法
dashboard 实时系统面板(CPU/内存/GC/线程) dashboard -i 2000(每2秒刷新)
thread 线程状态分析 thread -n 5(CPU Top 5线程), thread -b(检测死锁)
watch 方法执行观测(入参/返回值/异常) watch com.example.OrderService placeOrder '{params,returnObj,throwExp}' -x 3
trace 方法内部调用路径和耗时 trace com.example.OrderService placeOrder '#cost>100'(只显示耗时>100ms的调用)
monitor 方法调用统计 monitor -c 5 com.example.OrderService placeOrder(每5秒统计一次)
stack 查看方法被调用的路径 stack com.example.OrderService placeOrder
tt 方法执行时空隧道(记录+回放) tt -t com.example.OrderService placeOrder(记录),tt -i 1000 -p(回放)
profiler CPU Profiling(基于async-profiler) profiler start, profiler stop --format html(生成火焰图)
vmtool JVM对象查询 vmtool --action getInstances --className java.util.HashMap --limit 10
jad 反编译源码 jad com.example.OrderService placeOrder

3.2 典型诊断场景

场景1:接口变慢排查

# 1. 使用trace命令追踪方法调用链,找出耗时最长的子调用
trace com.example.controller.OrderController createOrder '#cost > 500'

# 输出示例(Arthas会自动统计每个方法的平均/最大/最小耗时):
# `---ts=2026-05-26 10:30:01;thread_name=http-nio-8080-exec-3;
#     [100% 2350ms] OrderController:createOrder()
#     `---[95% 2233ms] OrderService:placeOrder()
#         `---[80% 1786ms] PaymentService:processPayment()  # ← 瓶颈在此!
#             `---[95% 1697ms] HttpClient:post()

# 2. 进一步追踪瓶颈方法的内部调用
trace com.example.service.PaymentService processPayment '#cost > 1000'

# 3. 如果需要更详细的参数信息,使用watch命令
watch com.example.service.PaymentService processPayment '{params,returnObj,throwExp}' -x 3

场景2:CPU飙高排查

# 1. 使用dashboard查看整体CPU使用情况
dashboard

# 2. 找出CPU使用率最高的线程
thread -n 5

# 3. 启动CPU Profiling(基于async-profiler)
profiler start

# 等待30秒-1分钟收集数据...

# 4. 停止并生成火焰图
profiler stop --format html --file /tmp/cpu-flamegraph.html

# 5. 将火焰图下载到本地用浏览器打开分析

场景3:内存泄漏排查

# 1. 实时监控内存和GC情况
dashboard -i 2000

# 2. 生成堆内存直方图,查看哪些类占用内存最多
heapdump --live /tmp/heap.hprof

# 3. 查看着特定类的实例数量和内存占用
vmtool --action getInstances --className com.example.model.Order --limit 100

# 4. 配合jmap分析堆转储文件(需要在本地用MAT/JProfiler等工具打开heap.hprof)
# jmap -histo:live  | head -20  (也可以在服务器用JDK自带命令快速查看)

4. 数据库 Profiling

4.1 MySQL慢查询分析

数据库是大多数性能问题的源头。在性能压测和线上问题排查中,数据库Profiling是不可或缺的环节。

工具/方法适用场景核心能力
慢查询日志(Slow Query Log) 长期监控,发现历史慢SQL 记录执行时间超过 long_query_time 的SQL
pt-query-digest 慢查询日志分析 聚合分析慢查询日志,按执行频率和总耗时排序,生成分析报告
Performance Schema 实时SQL监控 MySQL内置的性能监控引擎,可追踪每条SQL的执行详情、锁等待、IO等
EXPLAIN SQL执行计划分析 分析单条SQL的执行计划(索引使用、扫描行数、排序方式等)
SHOW PROFILE SQL执行阶段分析 分解SQL各阶段耗时(连接、解析、优化、执行、返回)

4.2 Performance Schema实战

-- ============================================================
-- MySQL Performance Schema: 实时慢SQL分析
-- ============================================================

-- 1. 查看当前正在执行的SQL和耗时
SELECT
    r.THREAD_ID,
    r.SQL_TEXT,
    TIMESTAMPDIFF(SECOND, r.START_TIME, NOW()) AS exec_seconds,
    r.ROWS_EXAMINED,
    r.ROWS_SENT,
    r.LOCK_TIME / 1000000000 AS lock_seconds
FROM
    performance_schema.events_statements_current r
WHERE
    r.SQL_TEXT IS NOT NULL
    AND TIMESTAMPDIFF(SECOND, r.START_TIME, NOW()) > 2  -- 执行超过2秒的SQL
ORDER BY exec_seconds DESC;

-- 2. 按查询模式聚合统计Top慢SQL
SELECT
    DIGEST_TEXT,
    COUNT_STAR AS exec_count,
    ROUND(AVG_TIMER_WAIT / 1000000000, 2) AS avg_latency_ms,
    ROUND(MAX_TIMER_WAIT / 1000000000, 2) AS max_latency_ms,
    ROUND(SUM_ROWS_EXAMINED / COUNT_STAR) AS avg_rows_examined,
    ROUND(SUM_ROWS_SENT / COUNT_STAR) AS avg_rows_sent
FROM
    performance_schema.events_statements_summary_by_digest
WHERE
    DIGEST_TEXT IS NOT NULL
    AND AVG_TIMER_WAIT > 1000000000  -- 平均耗时 > 1秒
ORDER BY
    SUM_TIMER_WAIT DESC
LIMIT 20;

-- 3. 分析锁等待
SELECT
    r.trx_id AS waiting_trx_id,
    r.trx_mysql_thread_id AS waiting_thread,
    TIMESTAMPDIFF(SECOND, r.trx_started, NOW()) AS wait_seconds,
    r.trx_query AS waiting_query,
    b.trx_id AS blocking_trx_id,
    b.trx_mysql_thread_id AS blocking_thread,
    b.trx_query AS blocking_query
FROM
    information_schema.innodb_lock_waits w
JOIN
    information_schema.innodb_trx r ON w.requesting_trx_id = r.trx_id
JOIN
    information_schema.innodb_trx b ON w.blocking_trx_id = b.trx_id;

4.3 pt-query-digest使用

# pt-query-digest 是Percona Toolkit中的明星工具,用于分析慢查询日志

# 基本用法:分析慢查询日志
pt-query-digest /var/log/mysql/slow-query.log > slow_report.txt

# 分析最近1小时的慢查询
pt-query-digest --since=1h /var/log/mysql/slow-query.log

# 过滤特定数据库的查询
pt-query-digest --filter '$event->{db} eq "order_db"' /var/log/mysql/slow-query.log

# 生成详细报告(含SQL执行计划建议)
pt-query-digest --explain h=localhost,u=root,p=password /var/log/mysql/slow-query.log

# 输出报告包含:
# - 按总耗时排名的Top SQL
# - 每条SQL的执行次数、平均/最小/最大耗时
# - 执行时间分布图
# - 查询指纹(Query Fingerprint,将参数替换为占位符)

5. 火焰图

5.1 火焰图原理

火焰图(Flame Graph)由Brendan Gregg发明,是一种可视化CPU Profiling数据的绝佳方式。它将调用栈信息转化为一张"燃烧的图":

  • X轴(宽度):表示某个函数占用CPU的比例。越宽说明该函数(及其子调用)占用的CPU时间越多
  • Y轴(高度):表示调用栈的深度。自底向上是从入口函数到叶子函数的调用链
  • 颜色:通常随机着色用于区分不同函数块,无特殊含义

5.2 生成Java火焰图(async-profiler)

# ============================================================
# async-profiler 生成火焰图
# async-profiler 是目前Java生态中最推荐的生产级Profiler
# ============================================================

# 1. 下载 async-profiler
# https://github.com/async-profiler/async-profiler/releases

# 2. CPU Profiling (默认模式)
./profiler.sh -d 60 -f /tmp/cpu-flamegraph.html 

# -d 60: 采集60秒
# -f /tmp/cpu-flamegraph.html: 输出火焰图HTML文件

# 3. 内存分配Profiling (Allocation)
./profiler.sh -d 60 -e alloc -f /tmp/alloc-flamegraph.html 

# 4. Wall-Clock Profiling (墙钟时间,含等待/阻塞/IO,更全面)
./profiler.sh -d 60 -e wall -f /tmp/wall-flamegraph.html 

# 5. Lock Profiling (锁竞争分析)
./profiler.sh -d 60 -e lock -f /tmp/lock-flamegraph.html 

# 6. 在容器化环境中的使用
# (需要在容器启动时授予SYS_ADMIN能力或设置perf_event_paranoid)

# Docker运行时添加权限:
docker run --cap-add SYS_ADMIN ...

# 或在Kubernetes中通过securityContext配置:
# securityContext:
#   capabilities:
#     add: ["SYS_ADMIN"]

5.3 火焰图解读技巧

现象含义优化方向
顶部"平顶山"(宽而平坦) 某个函数自身消耗大量CPU(不含子调用),是CPU热点 优化该函数的算法复杂度、减少循环、使用缓存
高而窄的"塔" 深度调用链,可能存在不必要的多层封装或递归 简化调用链、减少中间层、优化递归为迭代
大量GC相关函数 GC过于频繁,堆内存压力大 检查对象分配热点、增大堆内存、优化GC策略
大量锁/同步相关函数 存在严重的锁竞争 减少锁粒度、使用无锁数据结构、异步化
IO/网络函数占比高 应用是IO密集型,CPU多数在等待IO 异步IO、连接池优化、批量操作、缓存
📖 火焰图阅读顺序 从底部向上阅读,从顶部发现问题。底部是程序的入口(如main/Thread.run),越往上调用栈越深。如果在顶部看到一个很宽的矩形,说明该函数自身消耗了大量CPU,这是优化的首选目标。如果在中间层看到很宽的矩形,说明该函数及其所有子调用共同消耗了大量CPU,需要向下钻取找到真正的瓶颈。

6. 在线 vs 离线 Profiling

6.1 两种分析策略对比

维度在线Profiling离线Profiling
定义 在运行中的JVM上实时采集性能数据 采集性能数据后,在独立的分析工具中离线分析
典型工具 Arthas、async-profiler、JFR(Java Flight Recorder) JProfiler、MAT(Memory Analyzer Tool)、YourKit
数据源 实时采样(CPU/内存/线程) Heap Dump、JFR录制文件、Core Dump
性能开销 低到中等(采样模式1-5%) 零(分析阶段不在目标JVM上)
适用环境 生产环境在线诊断、测试环境压测期间 本地开发分析、离线深度分析
优势 快速定位、所见即所得、无需停机 分析深入全面、可视化丰富、可反复分析
劣势 分析深度有限、有一定性能影响 需要提前采集数据、分析有滞后性

6.2 最佳实践:在线+离线结合

在实际性能优化工作中,通常采用"在线快速定位 + 离线深度分析"的组合策略:

  1. 在线快速定位(Arthas / async-profiler)
    • 压测期间或线上问题发生时,通过 dashboardthread -n 5 快速了解系统状态
    • 使用 trace 命令追踪可疑接口的方法级耗时分布
    • 启动 profiler start 采集30秒-2分钟CPU样本,生成火焰图初步锁定热点
    • 整个在线分析过程应控制在5-10分钟内,避免长时间Profiling影响业务
  2. 离线深度分析(JProfiler / MAT)
    • 在压测期间通过 jmap -dump:live,format=b,file=heap.hprof <PID> 或JFR录制采集Heap Dump
    • 将Heap Dump文件下载到本地,用JProfiler或MAT打开进行深度分析
    • 分析对象保留路径(Path to GC Roots)、Dominator Tree、泄漏嫌疑报告等
    • 离线分析时间不受限制,可以反复研究,对比不同时间点的快照
  3. 流程闭环
    • 在线定位热点 → 离线确认根因 → 修改代码 → 重新压测验证 → 在线确认改善
    • 将Profiling结果(火焰图、方法耗时排名)作为性能测试报告的一部分归档
    • 建立团队的性能基线Profiling数据,后续版本发布时对比差异

6.3 JFR(Java Flight Recorder)

JFR是JDK内置的低开销性能事件采集框架(JDK 11+开源),它可以在生产环境中长期开启(默认开销<1%),持续记录JVM的详细运行数据。JFR录制文件可以离线用JDK Mission Control(JMC)打开分析,是"在线采集 + 离线分析"的典型实践。

# ============================================================
# JFR 使用示例
# ============================================================

# 1. 启动JFR录制(2分钟,输出到文件)
jcmd  JFR.start name=perf_recording duration=120s filename=/tmp/recording.jfr

# 2. 查看录制状态
jcmd  JFR.check

# 3. 提前停止录制
jcmd  JFR.stop name=perf_recording

# 4. JVM启动参数:持续开启JFR(低开销模式)
java -XX:StartFlightRecording=filename=/opt/jfr/recording.jfr,maxsize=500M \
     -jar application.jar

# 5. JFR录制文件可以用JDK Mission Control (jmc) 打开
# jmc 提供:
# - CPU热点分析(类似火焰图)
# - 内存分配分析
# - GC事件分析
# - 锁竞争分析
# - IO活动分析
# - 异常分析
💡 Profiling工具选择决策树
  • 生产环境紧急排查 → Arthas(attach即用,无需重启)
  • 生产环境CPU热点 → async-profiler(极低开销,火焰图精美)
  • 长期持续监控 → JFR(JDK内置,<1%开销)
  • 深度内存分析 → JProfiler / MAT(Heap Dump离线分析)
  • 本地开发调试 → VisualVM(免费,JDK自带)
  • 容器化/云原生环境 → async-profiler(轻量,易集成)