news 2026/10/15 19:22:16

Java性能优化01:性能优化不是调参数,先建立目标、指标与优化闭环

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java性能优化01:性能优化不是调参数,先建立目标、指标与优化闭环

Java性能优化01:性能优化不是调参数,先建立目标、指标与优化闭环

Java 性能优化系列第 1 篇。本文先回答一个最基本的问题:怎样判断一次优化真的有效?CPU、内存、GC、多线程、JDK 21 虚拟线程和网络问题,会在后续文章中分别实践。

接口变慢后,很多人的第一反应是加线程、扩大堆内存、修改 GC 参数。这些办法有时有效,但也可能完全改错方向。

性能优化更像看病:先确认症状,再用检查结果找原因,最后用复查判断治疗是否有效。不能只因为改了参数、平均耗时下降,就宣布优化成功。错误率可能升高,少数请求可能变得更慢,代码甚至可能少做了业务工作。

开始优化前,先回答四个问题:

  1. 测的是哪个业务场景?
  2. 测试时有多少流量、多少数据?
  3. 哪个数字达到多少才算成功?
  4. 优化后业务结果是否仍然正确?

一、把“快一点”改成明确目标

“把报价接口优化一下”不是一个可以验收的目标。可以改成:

使用固定测试数据,持续压测 30 分钟。99% 的请求在 200 ms 内完成,也就是 p99 小于 200 ms。错误率小于 0.1%,单实例 CPU 不超过 70%。返回结果必须与优化前一致。

这句话说明了五件事:

要说明的内容例子
测什么/api/quote报价接口
怎么测固定数据,持续压测 30 分钟
多快算合格99% 的请求在 200 ms 内完成(p99 小于 200 ms)
能否出错错误率小于 0.1%
有什么限制CPU 不超过 70%,业务结果不能改变

这些目标不能凭感觉填写。它们应该来自用户能接受的等待时间、上游超时时间和机器成本。

二、性能至少要看四类数字

不能只看一个“平均响应时间”。一次测试至少要同时记录下面四类指标:

指标说人话常见单位或例子
吞吐量每秒真正完成了多少工作req/s、条/秒
延迟用户等了多久p50、p95、p99
错误率有多少请求失败或超时0.1%、1%
资源为这些结果花了多少机器资源CPU、内存、GC(垃圾回收)、线程、连接数

吞吐量:每秒完成多少请求

一个接口每秒返回 1,000 次,可以写成 1,000 req/s。但要注意,快速返回错误也会产生很高的 req/s,所以吞吐量必须和错误率一起看。

延迟:不要只看平均值

把 100 个请求按耗时从小到大排列:

  • p50 可以理解为中间水平,约有一半请求比它快。
  • p95 表示约 95% 的请求不超过这个耗时。
  • p99 关注最慢的那一小部分请求,也就是常说的“长尾”。

例如,99 个请求都用了 10 ms,只有 1 个用了 1 秒,平均值仍然只有约 19.9 ms。平均值看起来很好,但那个等待 1 秒的用户并不会感受到“平均”。

错误率:不能用失败换速度

优化后延迟下降,但错误率从 0.1% 升到 2%,通常不能算成功。除了 HTTP 500,还应统计超时、限流和业务校验失败。

资源:解释为什么慢

CPU 高,可能是计算太多;CPU 不高但延迟很高,线程可能在等数据库或下游接口;p99 周期性升高,可能与 GC 停顿有关。GC 是 JVM 自动清理不再使用对象的过程,清理期间可能短暂停顿应用。

这里的“可能”很重要。监控只能帮助提出猜测,不能直接证明原因。真正下结论前,还要查看线程、GC 日志、JFR(JDK 自带的性能记录工具)或火焰图(显示程序时间主要花在哪些方法上的图),并做对照实验。

补充:SLI、SLO 和 SLA

理解了指标之后,再看三个常见缩写就简单多了:

  • SLI 是实际成绩,例如本月 99.95% 的请求成功。
  • SLO 是目标成绩,例如本月成功率要达到 99.9%。
  • SLA 是对外承诺,没有达到时可能需要赔偿。

可以把它们记成:SLI 是成绩单上的分数,SLO 是目标线,SLA 是写进合同的承诺。

k6 压测通过,只能说明这次测试满足条件,不能证明整个月的生产服务都满足 SLO。


三、性能优化的正确顺序

一次完整优化可以概括成六步:定目标、测基线、找证据、改一处、重新测、再上线。

这里有三条硬规则:

  1. 业务结果不能变错:不能靠少查数据、少做计算换取低延迟。
  2. 前后测试条件要相同:改代码的同时换机器,就无法判断收益来自哪里。
  3. 测试要重复执行:第一次可能受 Java 预热、连接建立或缓存状态影响。

四、贯穿本系列的实验项目

本系列使用开源项目java-performance-lab。它提供三个接口和一个可控制延迟、失败的模拟下游服务。这里的“下游服务”,就是当前接口还要调用的另一个服务。

接口做什么后续用来测试什么
/api/search查询数据库并返回 JSON慢 SQL、连接池、分页、序列化
/api/quote读取缓存并调用下游接口超时、缓存、线程阻塞、虚拟线程、网络
/api/report做计算并批量处理数据CPU、对象分配、锁竞争、GC

后续文章都使用这三个接口。这样每次优化都能和前面的数据比较,不需要每篇重新编一个案例。


五、跑一次真实基线

“基线”就是修改代码之前的成绩。没有基线,优化后就没有可靠的比较对象。

测试环境

本次数据采集于 2026-10-14,应用代码版本为34f3562。

项目配置
操作系统Windows 11,压测工具和服务在同一台机器
CPUIntel Core i5-13500,14 核 20 线程
内存32 GB,应用容器没有单独限制 CPU 和内存
JavaEclipse Temurin 21.0.12.1 LTS
应用Spring Boot 3.4、H2、无 Redis 缓存、普通平台线程(传统 Java 线程)
压测工具k6 v2.1.0

压测端和服务端在同一台电脑,而且容器没有固定资源,因此这组数据只用于演示测试过程,不代表项目的最大容量。

怎么测

本次使用 k6 压测。k6 是一个开源压测工具,可以模拟多个用户反复访问接口,并统计吞吐量、响应时间和错误率。

基线脚本是load-tests/baseline.js。测试方式如下:

  • 使用 5 个虚拟用户,也就是让 k6 模拟 5 个并发用户,持续 20 秒。
  • 每轮同时请求三个接口,然后等待 200 ms。
  • 三个接口都必须返回 HTTP 200,也就是请求成功。
  • 整体错误率必须低于 1%,p95 必须低于 1 秒。

下面是一条完整命令,在 PowerShell、cmd 和 Bash 中都不需要处理换行符:

k6 run --summary-trend-stats "avg,min,med,p(90),p(95),p(99),max" --summary-export "blogs/assets/01/baseline-run-1.json" -e BASE_URL=http://localhost:8080 load-tests/baseline.js

仓库中的标准 Compose 环境使用 8080 端口。本文采集数据时,开发容器映射到了 18080;两者只是端口映射不同。

连续测三次

代码和测试条件都不变,连续执行三次:

轮次请求数吞吐p50p95p99错误率
第 1 次1,44071.69 req/s4.58 ms11.60 ms30.75 ms0%
第 2 次1,47073.07 req/s3.45 ms5.55 ms7.24 ms0%
第 3 次1,47073.23 req/s3.11 ms4.98 ms6.10 ms0%

数据说明:数据来自仓库中的三份baseline-run-*.json,图片由render-baseline-chart.ps1生成。三次测试的错误率都是 0%。

最明显的现象是:第一次的 p99 为 30.75 ms,第三次只有 6.10 ms,相差约 5 倍;但三次吞吐量很接近。

这只能证明一件事:这种短时间测试的长尾不够稳定,不能跑一次就挑一个好看的结果。

现在还不能断定是 JIT 预热造成的。JIT 会在程序运行期间,把经常执行的 Java 代码进一步编译成本机更容易执行的机器码,因此程序刚启动时和运行一段时间后,速度可能不同。连接建立、缓存和电脑上的其他任务也可能影响结果。要确定原因,需要延长测试时间,并结合 JFR(JDK 自带的性能记录工具)等证据,这会在后续文章中完成。

第 2 次测试中,三个接口的结果如下:

接口p50p95p99
/api/search3.01 ms4.15 ms6.78 ms
/api/quote4.53 ms5.88 ms7.14 ms
/api/report3.07 ms4.18 ms6.41 ms

报价接口略慢,但差距很小,还不能说明下游 HTTP 就是瓶颈。下一步应该增加下游延迟做对照实验,而不是马上修改线程池或 HTTP 参数。


六、补充知识:用利特尔法则理解“系统里有多少请求”

Little’s Law 中文常叫“利特尔法则”。名字听起来有些陌生,它表达的意思其实很直观:

请求来得越快,或者每个请求停留得越久,系统里同时存在的请求通常就越多。

公式如下:

系统内平均请求数 = 每秒处理的请求数 × 每个请求的平均停留时间(秒) L = λ × W
  • L是系统内的平均请求数,包括正在处理和排队等待的请求。
  • λ读作“兰姆达”,表示平均吞吐量,例如每秒处理 500 个请求。
  • W表示一个请求从进入到离开的平均时间,单位必须换算成秒。

先看一个容易理解的例子。一个接口平均每秒处理 500 个请求,每个请求平均停留 200 ms,也就是 0.2 秒:

500 × 0.2 = 100 个请求

这表示在稳定运行期间,系统中平均大约有 100 个请求正在处理或排队。它不代表线程池必须设置为 100,因为请求还可能受 CPU、数据库连接池、下游接口和线程模型影响。

再看本文的实测数据。第 2 次测试的吞吐量为 73.07 req/s,平均响应时间为 3.67 ms,也就是 0.00367 秒:

73.07 × 0.00367 ≈ 0.27 个请求

“0.27 个请求”是整个测试期间算出来的平均值,并不是说某个时刻真的存在四分之一个请求。这个数字小于 1,是因为接口响应很快,而且 5 个虚拟用户每请求一轮还会等待 200 ms;大部分时间里,应用内没有请求正在执行。

使用这个公式要满足三个条件:统计时间足够长、请求没有持续越积越多、吞吐量和响应时间使用同一个系统范围。它适合检查数据是否大致合理,但不能直接用来决定线程池大小,也不能把 p99 当成公式中的平均响应时间。


七、怎样提出一个可以验证的猜测

不要只说“虚拟线程更快”,而要把猜测写完整:

如果给下游增加 100 ms 延迟,普通平台线程(传统 Java 线程)会大量等待。启用 JDK 21 虚拟线程(一种可以大量创建的轻量线程)后,线程资源占用应该下降;但数据库连接数和下游处理能力没有变化,所以系统吞吐不会无限增加。

这句话说明了测试场景、准备修改的内容和预期结果。如果结果没有出现,这个猜测就可能是错的。

每次实验至少记录下面这些内容:

- 日期和代码版本: - JDK、机器或容器配置: - 测试数据和负载: - 优化目标: - 业务正确性检查: - 观察到的现象: - 本次只修改什么: - 优化前后数据: - 最终决定:保留、回滚还是继续实验?

更完整的模板在docs/results/README.md。


八、最容易踩的六个坑

  1. 只看平均值:平均值会隐藏少量特别慢的请求,至少还要看 p95、p99 和错误率。
  2. 只测一次:Java 预热、缓存和连接状态都会影响短测试。
  3. 测试时间太短:20 秒看不到内存逐渐增长、缓存过期和周期性 GC。
  4. 压测工具和服务抢同一台机器:正式容量测试应把压测端分开,并检查压测机自己有没有到上限。
  5. 优化前后做的工作不一样:少返回字段、跳过校验会让数字变好,但这不是正常优化。
  6. 一次修改很多地方:同时改 GC、线程池和缓存,即使变快,也不知道究竟是哪一项有效。

小结

第一次做性能优化,可以先记住下面这套简单方法:

  1. 写清业务场景和合格标准。
  2. 同时记录吞吐、延迟、错误率和资源。
  3. 修改前先测基线,而且要重复测试。
  4. 根据证据猜原因,一次只改一个主要因素。
  5. 用相同条件重新测试,并检查业务结果。
  6. 小流量上线,指标异常时能够回滚。

下一篇《Java性能优化:线上 Java 性能问题排查总纲,覆盖 CPU、线程、内存与 GC》会回答:CPU 高、线程卡住、内存上涨或频繁 GC 时,第一步应该看什么。文章会使用jcmd、JFR/JDK Mission Control、async-profiler 和 Arthas,并配上真实工具图。


系列导航:上一篇:无 | 系列目录 | 下一篇:Java性能优化:线上 Java 性能问题排查总纲,覆盖 CPU、线程、内存与 GC

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 19:18:56

LCD带真空测量气压表PCBA方案开发

气压表PCBA方案开发的核心需求,是完成高精度压力信号采集、运算处理、数据传输与数值显示,同时适配各类应用场景,满足低功耗、强抗干扰、小型化等差异化设计指标。西城微科依托芯片选型配套、算法开发、PCB布局设计的全链条服务能力&#xff…

作者头像 李华
网站建设 2026/10/15 19:18:38

C++11 新增知识点

可变参数模板 基本语法和原理 C11支持可变参数模板&#xff0c;也就是说支持可变数量参数的函数模板和类模板&#xff0c;可变数目的参数被称为参数包&#xff0c;存在两种参数包:模板参数包: 表示零或多个模板参数; 函数参数包: 表示零或多个函数参数。 template <class ..…

作者头像 李华
网站建设 2026/10/15 19:17:45

CAD协同设计对设计院到底有哪些好处?

导语&#xff1a;不少设计院对CAD协同设计还停留在『多花一笔钱』的印象。但真正做过的院都知道&#xff0c;它带来的不只是画图方式的变化&#xff0c;而是生产方式的升级。本文把CAD协同设计对设计院的好处讲透。 一、CAD协同设计的本质是什么&#xff1f; CAD协同设计&#…

作者头像 李华
网站建设 2026/10/15 19:17:03

MySQL 基础 + AI SQL 优化

楠风测开 测试工程师复习日记 Day15 用一套订单、支付和库存样例&#xff0c;把查询、关联、金额核对与事务验证串起来。 前言&#xff1a;SQL跑通之后&#xff0c;还差一次结果验收 朋友们好&#xff0c;我是楠风。 上一阶段聊完Linux监控&#xff0c;我们开始补上测试工程…

作者头像 李华
网站建设 2026/10/15 19:16:25

技术碾压:从网文创作到币圈实战的降维打击逻辑

重生之我在币圈当大佬42 技术碾压——从章节名到实战逻辑的创作与交易双线拆解看到“技术碾压”这五个字&#xff0c;我第一反应不是爽文桥段&#xff0c;而是币圈里每天都在真实发生的剧本——有人靠信息差赚钱&#xff0c;有人靠手速赚钱&#xff0c;而真正能称之为“碾压”的…

作者头像 李华