Java性能优化01:性能优化不是调参数,先建立目标、指标与优化闭环
Java性能优化01性能优化不是调参数先建立目标、指标与优化闭环Java 性能优化系列第 1 篇。本文先回答一个最基本的问题怎样判断一次优化真的有效CPU、内存、GC、多线程、JDK 21 虚拟线程和网络问题会在后续文章中分别实践。接口变慢后很多人的第一反应是加线程、扩大堆内存、修改 GC 参数。这些办法有时有效但也可能完全改错方向。性能优化更像看病先确认症状再用检查结果找原因最后用复查判断治疗是否有效。不能只因为改了参数、平均耗时下降就宣布优化成功。错误率可能升高少数请求可能变得更慢代码甚至可能少做了业务工作。开始优化前先回答四个问题测的是哪个业务场景测试时有多少流量、多少数据哪个数字达到多少才算成功优化后业务结果是否仍然正确一、把“快一点”改成明确目标“把报价接口优化一下”不是一个可以验收的目标。可以改成使用固定测试数据持续压测 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 日志、JFRJDK 自带的性能记录工具或火焰图显示程序时间主要花在哪些方法上的图并做对照实验。补充SLI、SLO 和 SLA理解了指标之后再看三个常见缩写就简单多了SLI 是实际成绩例如本月 99.95% 的请求成功。SLO 是目标成绩例如本月成功率要达到 99.9%。SLA 是对外承诺没有达到时可能需要赔偿。可以把它们记成SLI 是成绩单上的分数SLO 是目标线SLA 是写进合同的承诺。k6 压测通过只能说明这次测试满足条件不能证明整个月的生产服务都满足 SLO。三、性能优化的正确顺序一次完整优化可以概括成六步定目标、测基线、找证据、改一处、重新测、再上线。这里有三条硬规则业务结果不能变错不能靠少查数据、少做计算换取低延迟。前后测试条件要相同改代码的同时换机器就无法判断收益来自哪里。测试要重复执行第一次可能受 Java 预热、连接建立或缓存状态影响。四、贯穿本系列的实验项目本系列使用开源项目java-performance-lab。它提供三个接口和一个可控制延迟、失败的模拟下游服务。这里的“下游服务”就是当前接口还要调用的另一个服务。接口做什么后续用来测试什么/api/search查询数据库并返回 JSON慢 SQL、连接池、分页、序列化/api/quote读取缓存并调用下游接口超时、缓存、线程阻塞、虚拟线程、网络/api/report做计算并批量处理数据CPU、对象分配、锁竞争、GC后续文章都使用这三个接口。这样每次优化都能和前面的数据比较不需要每篇重新编一个案例。五、跑一次真实基线“基线”就是修改代码之前的成绩。没有基线优化后就没有可靠的比较对象。测试环境本次数据采集于 2026-10-14应用代码版本为34f3562。项目配置操作系统Windows 11压测工具和服务在同一台机器CPUIntel Core i5-1350014 核 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_URLhttp://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 代码进一步编译成本机更容易执行的机器码因此程序刚启动时和运行一段时间后速度可能不同。连接建立、缓存和电脑上的其他任务也可能影响结果。要确定原因需要延长测试时间并结合 JFRJDK 自带的性能记录工具等证据这会在后续文章中完成。第 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 λ × WL是系统内的平均请求数包括正在处理和排队等待的请求。λ读作“兰姆达”表示平均吞吐量例如每秒处理 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。八、最容易踩的六个坑只看平均值平均值会隐藏少量特别慢的请求至少还要看 p95、p99 和错误率。只测一次Java 预热、缓存和连接状态都会影响短测试。测试时间太短20 秒看不到内存逐渐增长、缓存过期和周期性 GC。压测工具和服务抢同一台机器正式容量测试应把压测端分开并检查压测机自己有没有到上限。优化前后做的工作不一样少返回字段、跳过校验会让数字变好但这不是正常优化。一次修改很多地方同时改 GC、线程池和缓存即使变快也不知道究竟是哪一项有效。小结第一次做性能优化可以先记住下面这套简单方法写清业务场景和合格标准。同时记录吞吐、延迟、错误率和资源。修改前先测基线而且要重复测试。根据证据猜原因一次只改一个主要因素。用相同条件重新测试并检查业务结果。小流量上线指标异常时能够回滚。下一篇《Java性能优化线上 Java 性能问题排查总纲覆盖 CPU、线程、内存与 GC》会回答CPU 高、线程卡住、内存上涨或频繁 GC 时第一步应该看什么。文章会使用jcmd、JFR/JDK Mission Control、async-profiler 和 Arthas并配上真实工具图。系列导航上一篇无 | 系列目录 | 下一篇Java性能优化线上 Java 性能问题排查总纲覆盖 CPU、线程、内存与 GC

相关新闻

信息系统开发与管理:从业务断点出发的实战方法论

信息系统开发与管理:从业务断点出发的实战方法论

1. 项目概述:这不是写代码,而是构建一套“会呼吸的业务神经”“信息系统开发与管理”这八个字,乍看像教科书目录里的一章标题,但在我带过的二十多个跨行业项目里,它从来不是程序员关起门来敲键盘的事——它是一群人围着…

2026/10/15 19:21:17 阅读更多 →
飞行影院买完三年不换片,老客为什么不来了?

飞行影院买完三年不换片,老客为什么不来了?

很多乐园开业时花大价钱上了飞行影院,头两个月排队,半年后没人进。老板想不通:设备没坏、屏幕没暗,怎么就没人了?答案不在设备,在片源。超元力球幕飞行影院,见过太多这种案例——设备是一次性投…

2026/10/15 19:21:17 阅读更多 →
openGauss 7.0 Docker 部署 + 跨版本迁移全流程(aarch64)

openGauss 7.0 Docker 部署 + 跨版本迁移全流程(aarch64)

openGauss 7.0 Docker 部署 跨版本迁移全流程(aarch64)沉淀范围:镜像资源获取 → Compose 部署 → 环境认知 → 跨版本数据迁移 → 全链路踩坑根因与解决方案 适用场景:arm/aarch64 环境、Docker 单机部署、旧版(RC3&…

2026/10/15 19:21:17 阅读更多 →

最新新闻

Apollo 11.0:面向车规级量产的自动驾驶仿真与通信架构解析

Apollo 11.0:面向车规级量产的自动驾驶仿真与通信架构解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 20:05:29 阅读更多 →
Spring Boot JPA动态SQL五大方案实战对比

Spring Boot JPA动态SQL五大方案实战对比

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 20:05:29 阅读更多 →
前端逻辑漏洞解析:从CTF抽奖题看客户端信任边界

前端逻辑漏洞解析:从CTF抽奖题看客户端信任边界

1. 这道题不是考运气,是考你有没有真正“看懂”前端逻辑“GWCTF 2019 WEB-枯燥的抽奖”——光看标题,很多人第一反应是:又一个靠爆破、猜接口、扫目录的CTF Web题?点开题目页面,一个极简界面:一个按钮写着“…

2026/10/15 20:05:29 阅读更多 →
Docker+Nginx+Gunicorn部署Python Web应用:从零到生产环境完整指南

Docker+Nginx+Gunicorn部署Python Web应用:从零到生产环境完整指南

把 Python Web 应用部署到服务器,几乎是每个写后端的人绕不开的一步。本地 debug 模式跑得风生水起,一旦要挂到公网上,各种问题就冒出来了:依赖装不上、进程没人管、端口冲突、静态文件 404……我自己第一次部署一个后台管理面板 …

2026/10/15 20:05:29 阅读更多 →
Unity场景OBJ导出方案:支持Terrain与FBX的标准化几何导出

Unity场景OBJ导出方案:支持Terrain与FBX的标准化几何导出

简介:这是一款面向Unity开发者(尤其是中高级用户)的场景导出工具插件,解决在Unity中将复杂场景(含GameObject与Terrain)或FBX模型高效转换为通用.obj格式的需求,广泛适用于Unity与外部建模/导航…

2026/10/15 20:05:29 阅读更多 →
基于geopandas的公交站点覆盖率分析方法与Python实现

基于geopandas的公交站点覆盖率分析方法与Python实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 20:04:27 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/15 16:18:54 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/15 19:41:51 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/15 13:48:15 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 9:49:14 阅读更多 →