调优三部曲/性能测试指标
性能调优:一场有节奏的战斗
大多数程序员第一次接触 JVM 调优,是在服务器报警「内存不足」或「CPU 飙升」的时候。仓促之下改参数、重启服务,问题可能暂时缓解,但下次还会再来。
真正的高手做调优,靠的不是直觉,而是一套可复用的方法论。这一节,我们来建立调优的全局观。
调优三部曲:诊断 → 定位 → 调优
调优不是一上来就改 -Xmx,而是按顺序做三件事:
第一步:建立性能基准(Before)
└── 不建立基准的调优都是瞎调
第二步:分析瓶颈(Finding)
└── 不定位瓶颈的调优都是猜
第三步:针对性调优(Action)
└── 不验证效果的调优都是自欺第一步:建立性能基准
调优之前,必须先知道「正常状态」是什么样的。
bash
# 查看当前 JVM 参数
java -XX:+PrintFlagsFinal -version 2>&1 | grep -i heap
# 启动应用,记录基础指标
jstat -gcutil <pid> 1000 10 # 每秒输出一次 GC 状态,共 10 次
# 记录正常时的吞吐量、延迟、内存占用关键指标包括:吞吐量(Throughput)、延迟(Latency)、内存占用(Footprint)。没有基准,调优前后的对比就无从谈起。
第二步:定位瓶颈
瓶颈通常在四个地方:
| 瓶颈类型 | 典型表现 | 诊断工具 |
|---|---|---|
| CPU 瓶颈 | CPU 使用率 100%,响应慢 | top、JProfiler、火焰图 |
| 内存瓶颈 | OOM、频繁 Full GC | jstat、MAT、heapdump |
| GC 瓶颈 | GC 时间过长、频繁 GC | jstat GC 日志 |
| I/O 瓶颈 | 线程阻塞在网络/磁盘 | jstack、async-profiler |
第三步:针对性调优
找到瓶颈后,对症下药:
内存不足 → 调堆大小、换 GC 策略
GC 频繁 → 调 GC 参数、减少对象分配
CPU 高 → 优化代码、用 profiler 找热点调优后必须再次测量,对比基准,确认效果。
性能测试指标
四大黄金指标
┌──────────────────────────────────────────────────┐
│ 四大黄金指标 │
├───────────────┬─────────────────────────────────┤
│ 吞吐量 │ 单位时间内处理的任务数 │
│ 延迟(Latency)│ 一个请求从发起到收到响应的时间 │
│ 内存占用 │ JVM 使用的堆内存大小 │
│ 错误率 │ 失败请求占总请求的比例 │
└───────────────┴─────────────────────────────────┘吞吐量(Throughput)
吞吐量指单位时间内系统处理的任务数量,通常用 TPS(每秒事务数)或 QPS(每秒查询数)衡量。
bash
# 用 JMeter 或 wrk 测试吞吐量
wrk -t12 -c400 -d30s http://localhost:8080/api
# 结果示例:
# Requests/sec: 12345.67
# Latency Distribution:
# 50% 12.3ms
# 90% 23.4ms
# 99% 45.6ms吞吐量不是越高越好——高吞吐量可能意味着资源耗尽,反而导致延迟上升。
延迟(Latency)
延迟是请求从发起到完成的时间。不同百分位代表不同含义:
| 百分位 | 含义 | 应用场景 |
|---|---|---|
| P50(中位数) | 50% 的请求在此时长内完成 | 日常监控 |
| P90 | 90% 的请求在此时长内完成 | SLA 承诺 |
| P99 | 99% 的请求在此时长内完成 | 性能警戒 |
| P999 | 99.9% 的请求在此时长内完成 | 极致优化 |
bash
# P99 延迟计算示例
# 假设 10000 个请求,延迟排序后
# 第 9900 个请求的延迟就是 P99
# P99 = 45ms → 99% 的请求在 45ms 内完成内存占用(Memory Footprint)
内存占用关注 JVM 实际使用的堆大小:
bash
# 查看堆使用情况
jmap -heap <pid>
# 输出示例:
# Heap Configuration:
# MaxHeapSize = 4294967296 (4096.0MB)
# InitialHeapSize = 268435456 (256.0MB)
#
# Heap Usage:
# Eden Space: capacity = 100663296 (96.0MB)
# used = 78594048 (74.9MB) ← 内存使用率
# free = 22069248 (21.1MB)错误率(Error Rate)
错误率衡量系统的稳定性:
错误率 = 失败请求数 / 总请求数 × 100%
常见的错误类型:
- HTTP 5xx 错误
- 超时(Timeout)
- 连接失败(Connection Refused)
- OOM(OutOfMemoryError)GC 性能指标
GC 是 JVM 调优中最常见的部分。以下是关键指标:
GC 频率
bash
# 查看 GC 频率和时长
jstat -gc <pid> 1000
# 输出:
# S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
# 10240.0 10240.0 0.0 0.0 81920.0 45127.4 163840.0 0.0 48640.0 46778.1 6144.0 5491.6 16 0.231 0 0.000 0.231关键列说明:
YGC → Young GC 次数(Minor GC)
YGCT → Young GC 总耗时(秒)
FGC → Full GC 次数(Major GC)
FGCT → Full GC 总耗时(秒)
GCT → GC 总耗时GC 评估标准
| 指标 | 可接受 | 需要关注 | 危险 |
|---|---|---|---|
| Minor GC 频率 | < 10 秒/次 | 1~10 秒/次 | < 1 秒/次 |
| Minor GC 耗时 | < 50ms | 50~200ms | > 200ms |
| Full GC 频率 | 几分钟/次或更少 | 几秒/次 | 更频繁 |
| Full GC 耗时 | < 1 秒 | 1~3 秒 | > 3 秒 |
| GC 期间停顿 | 无感知 | 轻微卡顿 | 明显卡顿 |
吞吐量计算
吞吐量 = (CPU 时间 - GC 时间) / CPU 时间 × 100%
假设:
CPU 总时间 = 60 秒
GC 时间 = 3 秒
吞吐量 = (60 - 3) / 60 × 100% = 95%
一般要求:吞吐量 ≥ 99%性能测试方法
压测工具对比
| 工具 | 适用场景 | 特点 |
|---|---|---|
| JMeter | 功能全面的企业级压测 | GUI 界面,功能全,但较重 |
| wrk | 轻量级 HTTP 压测 | 命令行,高性能,Lua 脚本扩展 |
| ** Gatling** | 代码化压测脚本 | Scala DSL,报告漂亮 |
| ab(Apache Bench) | 简单快速压测 | 单机简单场景 |
| hey | 轻量替代 ab | Go 编写,支持并发 |
压测步骤
bash
# 1. 预热:让 JIT 编译器充分优化
wrk -t4 -c100 -d60s http://localhost:8080/api
# 2. 正式测试:足够长的时间减少毛刺影响
wrk -t8 -c200 -d120s http://localhost:8080/api
# 3. 记录结果:包括延迟分布和吞吐量测试环境注意事项
测试环境要求:
1. 与生产环境配置相近(相同的 JVM 参数、相同的代码)
2. 隔离的网络环境(避免测试流量干扰生产)
3. 充足的资源(CPU、内存、网络带宽)
4. 预热后再测试(JIT 优化需要时间)性能调优 checklist
调优前,确保以下信息都已收集:
□ 吞吐量基准(TPS/QPS)
□ 延迟基准(P50/P90/P99)
□ 内存占用基准(堆使用量)
□ GC 频率和耗时基准
□ 错误率基准
□ 问题发生时的监控数据
□ 应用的 JVM 启动参数
□ 操作系统资源使用情况没有这些数据,调优就是在黑暗中射击。
本节小结
调优三部曲与性能指标核心要点:
| 维度 | 说明 |
|---|---|
| 诊断 | 建立性能基准,记录正常状态的指标 |
| 定位 | 找到瓶颈类型(CPU/内存/GC/I/O) |
| 调优 | 针对性调整,验证效果 |
| 吞吐量 | 单位时间处理任务数(TPS/QPS) |
| 延迟 | 请求响应时间,关注 P50/P90/P99 |
| 内存占用 | JVM 堆实际使用量 |
| GC 指标 | 频率、耗时、吞吐量损失 |
记住:没有基准的调优是猜测,没有验证的调优是冒险。
下一节,我们来看 jps/jstat/jinfo/jmap/jhat 命令。
