Skip to content

调优三部曲/性能测试指标

性能调优:一场有节奏的战斗

大多数程序员第一次接触 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 GCjstat、MAT、heapdump
GC 瓶颈GC 时间过长、频繁 GCjstat 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% 的请求在此时长内完成日常监控
P9090% 的请求在此时长内完成SLA 承诺
P9999% 的请求在此时长内完成性能警戒
P99999.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 耗时< 50ms50~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轻量替代 abGo 编写,支持并发

压测步骤

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 命令

基于 VitePress 构建