Skip to content

性能调优与故障排查 ​

Java 架构师 · 安全 · 调优 · AI · 前沿

性能调优是"先度量、再定位、后消除"——用数据定位瓶颈并消除它,而非凭感觉调参数。故障排查则是"在压力下快速还原现场、定位根因"。二者都依赖可观测数据与系统化方法论。本章从性能金字塔讲起,深入 JVM 调参(GC 选择/堆划分)、Arthas 在线诊断、慢 SQL 执行计划,以及网络/火焰图排查,并给出一套可复用的排查方法论。

一、性能金字塔与瓶颈定律 ​

性能优化要自顶向下逐层下钻,越底层改造成本越高:

  • 瓶颈定律(木桶效应):系统吞吐受最慢环节限制。先找到它,否则优化单点无益(缓存命中率 99% 但锁竞争严重,整体无提升)。
  • 二八法则:20% 的热点路径消耗 80% 的资源,先打热点。

二、JVM 调优:选 GC 与划堆 ​

2.1 GC 选型(核心权衡) ​

GC特点适用
G1可预测停顿(Region 化、增量回收)通用默认(JDK9+),平衡吞吐/延迟
ZGC亚毫秒级停顿(染色指针/并发)超低延迟(<10ms)、大堆(TB 级)
Shenandoah类似 ZGC,并发压缩低延迟替代
Parallel高吞吐、停顿长批处理/离线,不在意停顿
CMS(已废弃)—勿用

选型原则:延迟敏感(交易/网关)用 G1/ZGC;吞吐优先(离线计算)用 Parallel。ZGC 在超大堆下停顿仍极低,但吞吐量略低于 G1(并发开销)。

2.2 堆划分与关键参数 ​

-Xms4g -Xmx4g              # 堆初始=最大, 避免动态扩容抖动(GC 后扩堆触发)
-XX:+UseG1GC               # 选 G1
-XX:MaxGCPauseMillis=200   # 目标停顿(软目标)
-XX:InitiatingHeapOccupancyPercent=45  # 老年代占用触发并发标记
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/heap.hprof  # OOM 自动 dump
-Xlog:gc*:file=/data/gc.log:time         # GC 日志(分析 Full GC 频繁)

JVM 调优的致命误区

  • 盲目调大堆:堆太大 → Full GC 停顿漫长(G1 虽并发但 Mixed GC 仍可能长停顿)→ 反而更慢。按业务设定 + 选低延迟 GC。
  • Xms ≠ Xmx:堆动态扩容会触发额外 GC 与抖动,生产务必设相等。
  • 只盯吞吐忽略停顿:交易系统 Full GC 停顿 2s = 用户超时,吞吐再高也不可用。
  • 生产随意 jmap -F:jmap -F 会 STW 甚至挂起服务,优先用持续采样/低开销工具(如 GC 日志、JFR)。

三、Arthas:不重启的在线诊断 ​

Arthas 是阿里开源的 Java 诊断神器,无需重启、无需改代码即可在线看方法耗时、参数、调用栈——关键是它能在故障现场"抓现行"而不打断服务。

bash
# 追踪方法耗时(定位慢方法)
trace com.x.OrderService createOrder
# 观测参数与返回(看入参是否异常)
watch com.x.StockService deduct '{params,returnObj}' -x 3
# 看方法调用次数/成功失败
monitor com.x.PayService pay -c 5
# 反编译线上类(确认部署的是不是最新代码)
jad com.x.OrderService
# 实时看最忙线程(CPU 高时定位热点)
thread -n 3

排查矩阵:

现象工具动作
CPU 高top / arthas thread -n定位热点线程与方法
内存涨jstat / JFR / VisualVM看对象分布、GC 频率
慢 SQL慢查询日志 / Explain加索引/改写
阻塞arthas trace / watch找锁竞争点
网络netstat / tcpdump看连接数/重传

四、慢 SQL 与执行计划 ​

慢 SQL 是应用层最常见的瓶颈。用 EXPLAIN 看执行计划,关注:

  • type:从 ALL(全表扫描,最差)到 const(主键,最优)。出现 ALL/index 通常需加索引。
  • key:实际用的索引;NULL = 没用索引。
  • rows:预估扫描行数,越大越慢。
  • Extra:Using filesort/Using temporary = 额外排序/临时表,需优化。

常见慢 SQL 模式与优化:

  • 深分页 LIMIT 1000000,10:扫描百万行再丢弃。改游标/延迟关联:WHERE id > 上次最大id LIMIT 10。
  • N+1 查询:循环查关联(Hibernate lazy 经典坑)。改 JOIN / 批量(IN)一次取。
  • 索引失效:对索引列做函数(WHERE DATE(created)=...)、隐式类型转换、前导通配(LIKE '%x')→ 索引失效变全扫。

慢 SQL 上线前审查

慢 SQL 上线前用 Explain 审查,加索引优先于改代码。但索引不是越多越好——写多读少表索引多 = 写入慢、占用空间。按查询模式建复合索引(遵循最左前缀),用覆盖索引避免回表。

五、网络与火焰图排查 ​

  • 连接问题:netstat -an | grep ESTABLISHED | wc -l 看连接数;tcpdump 看重传/丢包。CLOSE_WAIT 堆积 = 代码没关连接(连接泄漏)。
  • 火焰图(Flame Graph):把采样到的调用栈横向堆叠,宽度 = 耗时占比。一眼看出"哪个方法吃 CPU"。可用 Async-Profiler 生成(低开销、生产可用):
bash
# 采样 CPU 30s 生成火焰图
asprof -d 30 -f flamegraph.html <pid>
  • JFR(Java Flight Recorder):JVM 内置低开销持续录制(方法、GC、锁、IO),生产可常开,事后分析根因。

六、系统化排查方法论 ​

调优铁律

  • 先建基线:调优前用全链路压测建立容量基线与 SLO,否则无法判断改动是否有效("感觉快了"不算)。
  • 改动可量化、可回滚:每次只改一个变量,对比前后指标;参数改动要能快速回滚。
  • 拒绝凭直觉调参:所有结论来自监控/Arthas/链路数据,不是"听说调大 XX 就好"。
  • 只优化瓶颈:先找最慢环节(木桶短板),避免局部优化整体无提升。

七、最佳实践 ​

  • 全链路压测建立容量基线与 SLO,调优以数据说话。
  • 用 Arthas/JFR/APM 在线诊断,避免重启打断现场。
  • 慢 SQL 上线前 Explain 审查,加索引优先于改代码;避免深分页/N+1。
  • GC 选 G1/ZGC(延迟敏感),堆 Xms=Xmx,OOM 自动 dump。
  • 建立"故障演练 + 复盘"机制,沉淀排查手册(Runbook)。

本节小结

性能调优是"先度量再改"的工程:用监控/Arthas/链路定位最慢环节,从架构(缓存/异步)到 JVM(GC/堆)逐层下钻。GC 选型按"延迟 vs 吞吐"权衡(G1 通用、ZGC 超低延迟、Parallel 高吞吐),堆务必 Xms=Xmx 且 OOM 自动 dump。Arthas 能不重启在线 trace/watch/thread 抓现场;慢 SQL 靠 Explain 看 type/key/rows/Extra 优化(深分页用游标、N+1 用批量、函数包裹索引失效)。网络用 netstat/tcpdump、CPU 用火焰图/Async-Profiler、根因用 JFR。铁律:建立基线、数据说话、改动可回滚、只优化瓶颈——拒绝凭直觉调参。架构师应把性能文化(压测+复盘)植入团队。

📚 官方文档与参考资料 ​

✏️ 练习与自测 ​

  1. 排查题:生产 CPU 100%、响应变慢。给出从定位到修复的系统化步骤(含 arthas 命令、火焰图、可能的根因:死循环/锁竞争/正则灾难)。
  2. 辨析题:Full GC 频繁的可能原因有哪些?如何区分"堆太小"与"内存泄漏"?分别给出验证手段与对策。
  3. 设计题:一条 SQL 在测试环境快、生产慢,列出你怀疑的原因(数据量/统计信息/索引选择性/参数嗅探/执行计划偏差)与验证手段(Explain/慢日志/绑定变量)。
  4. 设计题:设计一个"性能调优基线"流程:压测 → 采集哪些指标(QPS/P99/GC/CPU)→ 如何判断瓶颈在架构层还是 JVM 层 → 改动后如何验证有效。