Appearance
第69课:性能分析工具
🎯 学习目标
- 理解性能优化必须先测量再优化,不能凭感觉改代码。
- 掌握吞吐量、延迟、错误率、资源利用率等核心指标。
- 能使用 JDK 自带工具定位线程、内存、GC、类加载和 JVM 参数问题。
- 了解 JFR/JMC、Arthas、async-profiler、Micrometer 等常用工具的适用场景。
- 能建立一套从现象到根因的性能排查流程。
📖 一、性能分析的基本原则
性能优化最常见的错误是“看到一段代码觉得慢,然后开始改”。正确流程应该是:
text
定义目标 -> 收集指标 -> 复现问题 -> 定位瓶颈 -> 小步优化 -> 对比验证 -> 持续监控性能问题通常不是单点问题,而是多个因素叠加:
text
接口慢:可能是 SQL 慢、锁竞争、线程池满、GC 停顿、远程调用慢、连接池耗尽。
CPU 高:可能是死循环、序列化过重、正则回溯、热点方法、频繁 GC。
内存高:可能是缓存无界、集合未清理、线程本地变量泄漏、对象创建过多。没有指标,优化就是猜测。
📖 二、核心性能指标
| 指标 | 含义 | 关注点 |
|---|---|---|
| QPS/TPS | 每秒处理请求或事务数 | 系统吞吐能力 |
| 平均响应时间 | 请求平均耗时 | 容易被极端值掩盖 |
| P95/P99 | 95%/99% 请求的响应时间 | 更能反映用户体验 |
| 错误率 | 请求失败比例 | 性能压测中必须同时观察 |
| CPU 使用率 | 计算资源占用 | 高 CPU 不一定是坏事,长期 100% 才危险 |
| 内存使用 | 堆、非堆、直接内存 | 看趋势和 GC 后是否回落 |
| GC 暂停 | 垃圾回收停顿时间 | 影响尾延迟 |
| 线程数 | 活跃线程、阻塞线程 | 判断线程池和锁竞争 |
| 连接池 | 数据库/Redis/HTTP 连接占用 | 连接耗尽会拖慢全局 |
尤其要关注 P95/P99。平均耗时 50ms 并不代表系统健康,因为可能 99% 的用户都在等 2 秒。
📖 三、JDK 自带工具
1. jps:查看 Java 进程
bash
jps -lv输出示例:
text
12345 com.example.Application -Xms512m -Xmx512m2. jcmd:综合诊断入口
bash
jcmd 12345 VM.flags
jcmd 12345 VM.system_properties
jcmd 12345 Thread.print
jcmd 12345 GC.heap_infojcmd 是现代 JDK 中非常推荐的入口工具,很多老工具能力都可以通过它完成。
3. jstack:线程快照
bash
jstack 12345 > thread.txt重点查看:
text
BLOCKED:锁竞争
WAITING/TIMED_WAITING:等待条件、队列、IO
RUNNABLE:正在运行,可能消耗 CPU
deadlock:死锁信息如果 CPU 很高,可以结合系统线程 ID 定位热点线程:
bash
top -H -p 12345
printf "%x\n" 线程ID
jstack 12345 | grep -A 30 nid=0x十六进制ID4. jmap:堆信息和堆转储
bash
jmap -histo:live 12345 | head
jmap -dump:live,format=b,file=heap.hprof 12345堆转储文件可以用 Eclipse MAT、JProfiler 或 VisualVM 分析。
注意:dump 堆可能暂停应用,并产生很大文件,生产环境要谨慎。
5. jstat:GC 观察
bash
jstat -gcutil 12345 1000 10表示每 1 秒输出一次,共 10 次。重点看:
text
YGC/YGCT:年轻代 GC 次数和耗时
FGC/FGCT:Full GC 次数和耗时
GCT:总 GC 时间Full GC 频繁通常是严重信号。
📖 四、JFR 与 JMC
JFR 是 Java Flight Recorder,JMC 是 Java Mission Control。它们适合低开销采集线上 JVM 运行数据。
启动采集:
bash
jcmd 12345 JFR.start name=profile duration=120s filename=profile.jfr settings=profile停止采集:
bash
jcmd 12345 JFR.stop name=profileJFR 可以看到:
text
CPU 热点方法
对象分配热点
GC 暂停
锁竞争
线程状态
文件和 Socket IO
异常抛出频率JFR 的价值在于把 JVM 内部事件放到同一条时间线上,适合排查“某段时间突然变慢”的问题。
📖 五、Arthas
Arthas 是阿里开源的 Java 诊断工具,适合在线排查。
常用命令:
bash
dashboard # 查看整体状态
thread # 查看线程
jad 类名 # 反编译类
watch 类 方法 '{params, returnObj, throwExp}' -x 3
trace 类 方法 # 查看方法内部调用耗时
monitor 类 方法 # 统计方法调用次数和耗时示例:定位某个 Service 方法为什么慢:
bash
trace com.example.OrderService createOrder它会显示方法内部每个调用节点的耗时,适合快速判断慢在 SQL、远程调用还是本地计算。
注意:watch、trace 对高频方法有性能影响,生产环境要限制次数和条件。
📖 六、async-profiler
async-profiler 常用于 CPU 火焰图和分配火焰图。
CPU 采样:
bash
./profiler.sh -d 60 -e cpu -f cpu.html 12345对象分配采样:
bash
./profiler.sh -d 60 -e alloc -f alloc.html 12345火焰图阅读方法:
text
横向宽度:采样占比,越宽越热点
纵向层级:调用栈深度
顶部宽块:最终消耗 CPU 的方法不要只看最上层业务方法,要顺着调用栈看真正耗时的是序列化、正则、集合操作、锁,还是第三方库。
📖 七、应用层监控
Spring Boot 常用 Micrometer + Actuator 暴露指标:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>yaml
management:
endpoints:
web:
exposure:
include: health,metrics,prometheus常见指标:
text
http.server.requests
jvm.memory.used
jvm.gc.pause
jvm.threads.live
hikaricp.connections.active
system.cpu.usage这些指标可以接入 Prometheus + Grafana,形成趋势图和告警。
📖 八、压测工具
常见压测工具:
| 工具 | 场景 |
|---|---|
| JMeter | GUI 友好,适合复杂压测计划 |
| k6 | 脚本化,适合 CI/CD 和云原生 |
| wrk | 轻量 HTTP 压测 |
| ab | 简单快速,但能力有限 |
压测时必须记录:
text
压测机器配置
目标机器配置
并发数
持续时间
请求数据规模
预热时间
QPS
P95/P99
错误率
CPU/内存/GC/连接池指标只给一个“QPS 多少”没有意义。
⚠️ 九、常见陷阱
1. 本地结果代表线上
本地机器、数据量、网络、JVM 参数都不同,性能结论不能直接外推到生产。
2. 只看平均值
平均响应时间会掩盖尾延迟。企业系统更关注 P95/P99。
3. 没有基线
优化前没有记录指标,优化后就无法证明有效。
4. 采样工具影响系统
诊断工具也有开销。生产环境使用 trace、堆 dump、频繁日志时要谨慎。
🆚 十、Java vs C 对比
| 维度 | C/C++ | Java |
|---|---|---|
| CPU 分析 | perf、gprof、flamegraph | JFR、async-profiler、Arthas |
| 内存问题 | malloc/free、ASan、Valgrind | heap dump、GC 日志、MAT |
| 运行时 | 更接近系统层 | JVM 运行时提供大量事件 |
| 停顿问题 | 通常来自锁/IO | 还要关注 GC 和 JIT |
Java 性能分析不能只看操作系统指标,必须结合 JVM 内部指标。
💡 十一、最佳实践
- 优化前先定义目标,例如 P99 从 800ms 降到 300ms。
- 每次只改一个变量,否则无法判断效果来源。
- 先看大头:SQL、远程调用、锁、GC、连接池,别先抠微优化。
- 压测必须预热,避免 JIT 和缓存冷启动影响结论。
- 生产环境长期保留基础指标,问题发生后才有历史对比。
- 堆 dump、线程 dump、JFR 文件要按时间和版本归档,便于复盘。
- 性能优化结论必须用数据证明,不用“应该会快”这种表达。
🎓 小结
性能分析工具的价值是帮你把“系统很慢”拆成可验证的问题:慢在哪个接口、哪个阶段、哪个资源、哪个方法。掌握工具不是目的,建立“指标驱动、证据驱动”的排查流程才是核心。
🧭 十二、性能排查剧本
线上接口突然变慢时,可以按这个顺序处理:
text
1. 先确认影响范围:单接口、单实例、单用户、全部流量?
2. 看入口指标:QPS、P95、P99、错误率是否同时变化?
3. 看资源指标:CPU、内存、GC、线程、连接池。
4. 看依赖指标:数据库、Redis、消息队列、HTTP 下游。
5. 抓线程快照:是否大量 BLOCKED 或连接池等待?
6. 抓 JFR 或 profiler:CPU 热点和对象分配热点在哪里?
7. 对比变更:最近是否发布、改配置、改 SQL、扩容或缩容?
8. 小范围验证:灰度回滚或单点修改确认根因。
9. 形成复盘:记录症状、指标、根因、修复和预防措施。示例判断:
text
CPU 高 + RUNNABLE 线程多:优先看 CPU profiler。
CPU 低 + 线程大量 WAITING:优先看下游等待和连接池。
GC 时间升高 + 内存 GC 后不回落:优先看 heap dump。
P99 升高但平均值稳定:优先看锁竞争、慢 SQL、少量慢依赖。
错误率升高 + 队列堆积:优先看背压和拒绝策略。📌 十三、工具选择速查
| 问题 | 首选工具 |
|---|---|
| 不知道 Java 进程号 | jps -lv |
| 看 JVM 参数 | jcmd <pid> VM.flags |
| 看线程卡在哪里 | jstack、Arthas thread |
| 看方法调用耗时 | Arthas trace |
| 看 CPU 热点 | JFR、async-profiler |
| 看对象分配热点 | JFR、async-profiler alloc |
| 看堆里谁占内存 | jmap + MAT |
| 看 GC 频率和暂停 | GC 日志、jstat |
| 看接口趋势 | Micrometer + Prometheus + Grafana |
| 做 HTTP 压测 | k6、JMeter、wrk |
不要迷信单一工具。复杂问题通常需要指标、日志、线程栈、JFR 和业务上下文一起看。
🔍 十四、自测问题
text
为什么 P99 比平均响应时间更重要?
CPU 高时如何定位到具体 Java 方法?
线程大量 BLOCKED 通常说明什么?
Full GC 频繁应该收集哪些证据?
为什么压测必须记录错误率?
Arthas trace 为什么不能长时间挂在高频方法上?
JFR 相比普通日志有什么优势?
没有优化前基线,为什么很难证明优化有效?📌 十五、学习建议
建议准备一个简单 Spring Boot 接口,故意制造三种问题:
text
CPU 密集:循环计算哈希。
内存压力:持续创建大对象。
线程阻塞:sleep 或等待锁。然后分别使用:
text
jstack 看线程状态
JFR 看 CPU 和分配热点
jstat 看 GC 变化
Arthas trace 看方法耗时性能工具只有在真实现象中使用过,才能在生产事故中迅速判断该用哪一个。
📌 十六、报告格式
性能分析结论建议写成:
text
现象:
影响范围:
证据:
根因:
修复:
验证:
预防:不要只写“已优化”。