
1. 压测最怕的不是报错而是报错看不懂做性能测试这几年我用 Jmeter 压过的接口少说也有几百个。说实话工具本身不难难点全在那些说不上哪里不对但结果就是不对的鬼问题上。我见过不少同事脚本能跑、报告能出但一看结果就懵——吞吐量忽高忽低响应时间偶尔飙到天上跑到一半客户端直接无响应。这些问题的根源往往不在压测目标系统而在 Jmeter 本身。所以我把这些年踩过的坑按从装环境到分析结果的顺序整理了 8 个最常见的问题每个都附带排查思路和最终解决方案。如果你是刚接触压测的新手这篇文章能帮你少走不少弯路如果你已经写了不少脚本里面提到的几个隐蔽问题也值得对照检查一下。提示本文所有操作基于 Jmeter 5.x 版本JDK 为 8 或 11。不同版本在界面细节上略有差异但核心原理通用。2. 第一个问题JDK 版本与内存配置环境没搭对后面全是徒劳2.1 JDK 版本选择不是越新越好Jmeter 官方文档写得很清楚Jmeter 5.x 需要 JDK 8 及以上Jmeter 5.6 开始才支持 JDK 17。但实际工作中我发现很多人图省事装了最新的 JDK 21结果打开 JMeter 启动脚本时直接报UnsupportedClassVersionError。这个问题最坑的地方在于错误信息藏在 jmeter.log 或启动终端里如果你习惯双击 jmeter.bat 启动窗口一闪而过根本看不到报错。正确的做法是先敲命令java -version确认当前默认 Java 版本确认版本后编辑 Jmeter 安装目录下的bin/jmeter.batWindows或jmeter脚本Linux/macOS在文件头部加上JAVA_HOME指定路径# Windows 示例在 jmeter.bat 开头加上 set JAVA_HOMEC:\Program Files\Java\jdk1.8.0_202再检查jmeter.bat里的HEAP参数默认是-Xms1g -Xmx1g -XX:MaxMetaspaceSize256m为什么要特意强调这个因为压测场景下默认 1G 堆内存真的不够用。我曾经压一个分页查询接口线程数开到 200跑了 10 分钟直接把 Jmeter 跑出OutOfMemoryError。排查的时候打开聚合报告发现吞吐量曲线从中间开始断崖式下跌一开始还以为是服务器扛不住了看了压测机日志才知道是 Jmeter 自己内存不够在疯狂 Full GC。2.2 压测机内存规划堆内存和系统内存要分开算这里分享一个比较实用的经验值单台压测机跑 GUI 模式做压测线程数不超过 300超过 300 建议用非 GUI 模式命令行模式跑或者直接上分布式。别迷信我机器 16G 内存很够用Jmeter 的堆内存设置太大的话GC 停顿反而会让采样出现毛刺。个人习惯是控制线程数 500 以内时设置 JVM 堆为机器物理内存的一半但不超过 8G。也就是-Xms4g -Xmx4g。压测机的操作系统也要预留至少 2G 物理内存给网络栈和文件缓存否则 TCP 连接多了以后系统层面的连接表溢出会比 Jmeter 的 OOM 更早出现。注意在 bin/jmeter 脚本里改 HEAP 参数时别改-Xmx的同时忽略-Xms。两者设成一样能避免 JVM 动态伸缩带来的性能抖动这是我压长稳测试时特别注意的一点。2.3 界面布局错乱高分屏与缩放比例的问题接下来是一个很诡异但出现频率极高的界面问题——窗口控件重叠、撕裂。这个问题在 Windows 高分屏尤其常见。原因是 Jmeter 基于 AWT/Swing对操作系统的高 DPI 缩放支持不够好。你在 Windows 显示设置里把缩放调成 125% 或 150%Jmeter 界面就会错乱。解决办法有两个右键点击 jmeter.bat 或 jmeter.sh在兼容性设置里勾选替代高 DPI 缩放行为选择应用程序或者在jmeter.bat中添加一行启动参数-Dsun.java2d.uiScale1Java 9 及以上版本这个参数是让 Jmeter 强制按照 100% 缩放渲染界面虽然字会小一点但至少不会按钮叠按钮、树形菜单和右侧面板挤成一团。3. 第二个问题HTTPS 脚本录制失败证书信任链条没打通3.1 浏览器代理配置正确但依然无法录制很多人在录制 HTTPS 脚本时候明明开启了 HTTP(S) Test Script Recorder浏览器代理也指到了 127.0.0.1:8888但一访问 HTTPS 页面就报证书错误。道理很简单Jmeter 是用自己的 CA 证书来做中间人解密你本机不信任这个 CA浏览器自然拦截掉。具体操作如下先启动 HTTP(S) Test Script Recorder点击界面上的HTTPS Domains旁边的按钮生成根证书文件ApacheJMeterTemporaryRootCA.crt默认在 Jmeter 的bin目录下在浏览器里导入这个证书。以 Chrome 为例打开设置 → 隐私和安全 → 安全 → 管理证书 → 受信任的根证书颁发机构导入导入要选受信任的根证书颁发机构不选的话浏览器还是报错但这里还有个容易被忽略的点证书生成时带有机子名信息如果你换了电脑或者把 Jmeter 目录搬家了证书会重新生成那就要重新安装一遍。此外如果你录制的目标服务有证书锁定Certificate Pinning即客户端内置了特定的服务器证书指纹代理录制会失败这种情况只能退回到手动写请求别在录制上死磕。3.2 录制脚本出现大量 404并不是脚本问题另一个录制后常见情况录下来的脚本回放时大量 404。这通常不是请求路径不对而是缺失了前置请求的上下文。比如你录了一个登录接口再录了个人信息接口但 Jmeter 不会自动帮你维护 Cookie 和动态 Token。我的经验是清理录制脚本后把登录接口单独拿出来配合HTTP Cookie 管理器放在线程组的顶层再通过后置处理器提取登录响应里的 Token。HTTP Cookie 管理器只要放在线程组层级下就会自动管理同一个线程迭代中的所有 Cookie不需要额外配置。4. 第三个问题上传文件时的中文文件名乱码4.1 乱码的根因层级有多层别只改一处工作中处理过很多次文件上传接口的压测最常见的一个怪象是本地脚本文件上传成功但文件名到服务端就成了乱码而且还不是每次都乱偶尔又是正常的中文。第一层问题出在 Jmeter 的取样器编码。HTTP 请求取样器底部有个Content-Type和参数区域上传文件时 Jmeter 默认会使用 multipart/form-data。中文文件名乱码通常要检查三个地方HTTP 请求里的内容编码字段改成UTF-8bin/jmeter.properties里的sampleresult.default.encodingUTF-8系统属性里添加file.encodingUTF-8但这套组合下来我发现还不够。真正导致偶尔乱、偶尔不乱的隐藏原因是你本机操作系统编码与请求头不一致。Windows 中文版默认编码是 GBK发起 HTTP 请求时如果你在 BeanShell 或 JSR223 脚本里拼接了文件名字符串内部的编码就是 GBK传到服务端后如果服务端用 UTF-8 解析中文必然乱码。4.2 稳妥的做法用源文件路径名代替动态生成的文件名为了彻底规避这个坑我在压测上传接口时已经不再动态拼接文件名了。直接准备一批固定命名的中文文件比如测试报告_001.pdf放在压测机本地Jmeter 直接读取文件路径作为上传文件的原始名称。文件名: C:\jmeter_upload_files\测试报告_001.pdf 参数名称: file MIME类型: application/pdf这样 Jmeter 会从操作系统文件系统直接读取二进制内容和原始文件名不会经过脚本里的字符串转码文件名和服务器的编码互换逻辑保持一致就不会乱码。如果真的需要在脚本里动态构造文件名那就在 JSR223 预处理脚本里把所有字符串强制编码成 UTF-8 字节数组再传给取样器不要直接传字符串对象。4.3 压测机上的文件目录别用中文路径另外一个和上传压测强相关的小坑压测机上 Jmeter 的安装路径、脚本保存路径、上传文件所在路径都不要出现中文或特殊符号。我遇到过一整套脚本在本机运行正常拷到服务器上就报FileNotFoundException排查半天发现是服务器上文件夹名字带了个空格。Jmeter 对路径中的空格处理不够稳健尤其是在 CSV 配置文件路径上少走弯路。5. 第四个问题参数化和断言里的 BeanShell 代码慢得让人怀疑人生5.1 为什么建议你放弃 BeanShell改用 JSR223标题里带了 BeanShell 和断言我就直说了BeanShell 在 Jmeter 里能不用就别用。BeanShell 是解释执行的脚本引擎每执行一次都要解析脚本压测场景下成千上万次迭代解释执行的性能开销会被成倍放大。我做过一个对比同样是解析 JSON 提取某个字段BeanShell 后置处理器的平均耗时是 JSR223 Groovy 的 5 倍左右。在高并发下这个差距直接会拉低 Jmeter 自身的吞吐量上限导致压测结果失真。正确的替代方案用 JSR223 取样器或 JSR223 后置处理器脚本语言选 Groovy。Groovy 是 Jmeter 内置支持的性能接近原生代码而且调用 Java 库非常方便。// JSR223 后置处理器中的示例提取 JSON 响应中的 token import groovy.json.JsonSlurper def response prev.getResponseDataAsString() def json new JsonSlurper().parseText(response) vars.put(token, json.data.token as String)注意JSR223 脚本里别用eval或parseText去解析动态生成的脚本文本保持脚本静态、数据动态的原则。这一点直接影响 Jmeter 的稳定运行。5.2 断言一堆却测不出问题是断言写歪了很多人做压测时的断言设计就一句话响应里包含某个关键词。这种断言误判率极高。比如你断言成功两个字结果响应里主要在说操作失败请稍后重试——整个页面包含失败就必然包含操作你的断言照样通过。我通常会在断言里至少做三层检查状态码断言响应代码必须是 200 或预期的码响应体断言关键业务字段值精确匹配而不是模糊包含响应时间断言超过阈值则直接标记失败这三层用 Jmeter 自带的能力就能实现不需要写脚本。如果接口返回的是 JSON建议直接上JSON 断言这是 Jmeter 5.x 自带的重型断言可以定位路径比如$.data.status是否等于success比响应断言可靠得多。5.3 断言失败会重试这可能是你吞吐量上不去的原因线程组循环次数设成 5但日志里出现大量相同的失败请求看起来像是系统自动重试。实际上不是重试而是线程组循环本来就会反复执行。如果你的断言逻辑里包含失败时给响应写入一个标记之类的代码又没设置线程退出策略那压测就会越跑越乱。如果你希望压测时一旦断言失败就立即停止整场压测需要加一个BeanShell 监听器或 JSR223 监听器在isSuccess()为 false 时调用ctx.getEngine().stop()。不过实际压测中我一般不建议这么干因为一次偶发的超时可能只是网络抖动停掉整场压测有点因噎废食。更好的做法是让压测跑完最后在结果里过滤失败样本单独分析。6. 第五个问题聚合报告读不懂压测结果就白测了6.1 平均响应时间是最没用的指标聚合报告里的平均值可能是最误导人的指标。真实研发环境中的响应时间分布几乎不可能完全均匀压测时常常是 90% 的请求在 200ms 内完成剩下 10% 在 2 秒以上。平均值计算出来可能在 380ms 左右。这个数据说明什么其实什么也说明不了。要么你只关注了平均指标没看长尾分布要么你直接拿平均值去评估线上容量结果低估了系统压力。正确的做法是看 90 线90th pct和 99 线99th pct。聚合报告里默认就有这列数据。如果 95 线还是 300ms但 99 线飙到 5 秒那说明系统在极端情况下存在明显的等待链。这个时候你应该去查是不是有锁竞争、线程池排队或者依赖服务的超时重试逻辑在起作用。6.2 吞吐量忽高忽低先看是不是压测机自己的 GC 在捣乱很多压测分析到最后才发现问题出在 Jmeter 本身。非 GUI 模式下Jmeter 输出日志文件.jtl每记录一次采样都会伴随一次 I/O。如果结果文件放在机械硬盘上或者放在网络盘上写入延迟会直接干扰采样间隔。一个我常用的做法结果文件名写成固定路径同时开-e -o参数让 Jmeter 直接生成 HTML 报告。打开bin/jmeter.properties找到# 结果文件写入模式改成异步写入降低 I/O 对采样的影响 jmeter.save.hesertialResponseTimetrue jmeter.save.hesertialLatencytrue resultcollector.action_if_errorcontinue但更关键的是把结果的采样级别调低比如只在事务结束时记录而不要在请求结束时记录。用Transaction Controller勾选Generate parent sample聚合报告才会按完整业务流程算一个事务不然你看到的全是碎片的接口级采样业务级性能曲线被稀释掉。7. 第六个问题分布式压测中看起来连上了但数据没回来7.1 Agent 连不上 Controller十有八九是端口和网络策略做分布式压测时Controller 负责调度Agent 负责执行。这个架构本身不复杂但 Agent 连接不上是高频问题。第一件事检查 agent 机器的jmeter-server是否启动了启动后默认监听端口是 1099。但还有一个隐藏端口RMI 动态分配的端口。Controller 连上 1099 后会通过 RMI 再去连一个随机端口这个随机端口会在 server 端启动时打印在日志里。如果 Controller 和 Agent 之间有防火墙只放行 1099 端口照样连不上。正确做法是在 agent 的jmeter.properties里显式指定 RMI 固定端口server.rmi.port12345 server_port1099同时在jmeter-server启动脚本里加上-Djava.rmi.server.hostnameagent的实际IP不写 IP 的话 RMI 用的是本机 hostname 解析结果在 DNS 不通的内网环境会直接注册失败。7.2 远程执行结果为空先看报告收集目录命名连上之后另一个常见问题是 Controller 上能发起测试但最后生成的报告里只有 Controller 本机的数据Agent 的数据全丢了。这个坑往往出现在结果文件命名上。如果你在 CLI 模式远程启动时指定的-l路径名在每台 Agent 上重复且 Controller 通过某种机制合并结果就可能因为 Agent 本地写文件冲突而丢失或覆盖。稳妥的做法是指定结果文件路径时让每台 Agent 写到不同的目录或直接用-R远程启动后让 Controller 统一收集远程样本。Jmeter 5.x 在远程执行时所有 Agent 的采样数据都是回传给 Controller 内存再由 Controller 写入最终文件理论上不存在本地丢失问题。但如果你为了图省事用了-r并在每个 Agent 也配了-l就要考虑文件覆盖风险。实际执行命令建议如下jmeter -n -t test_plan.jmx -R 192.168.1.101,192.168.1.102 -l /data/result/remote_run.jtl -e -o /data/report/html7.3 Agent 的线程数不只是线程数还有两倍的连接开销分布式压测时每个 Agent 的线程组并发数设置要特别注意线程数 × 每线程的请求数直接决定了 Agent 能维持的活跃连接数。假如你设置了 200 线程循环 10 次单接口 HTTP 请求KeepAlive 开启的情况下实际上 Agent 可能会同时维持 200 个 TCP 连接。如果压测目标是 HTTPSTLS 握手还会额外增加内存和 CPU 开销。建议每台 Agent 的并发线程数控制在 500 以内。如果目标总并发是 2000那就用 4 台 Agent而不是硬挤在 1 台机器上跑 2000 线程。这是我从实践中得出的经验多花点机器成本比花几天时间排查 GC 毛刺和数据失真划算得多。8. 第七个问题录制 HTTPS 脚本后的 Cookie 和 Token 维护8.1 为什么录制的脚本回放时频繁 302 或 401这个问题在接口压测中几乎是最常见的录制的脚本能跑但跑出来的全是 302 跳转或者在登录接口后请求业务接口时返回 401 未认证。原因很简单录制下来的脚本默认只是把它看到的 HTTP 请求按原样回放而真实浏览器在登录后会在每个请求头里带上 Cookie 和鉴权头。Jmeter 通过代理录制时虽然它记录到了请求头但如果你把录制内容按事务拆分了跨事务的回放在两次请求之间没有维持同一个 Cookie 上下文。解决办法在线程组下添加HTTP Cookie 管理器它会根据 Set-Cookie 响应自动维护会话如果目标接口用的是Authorization: Bearer token用正则表达式提取器或 JSON 提取器从登录接口的响应中提取 token然后通过${token}变量引用在HTTP Header Manager里使用${token}动态值不要硬编码8.2 登录接口和业务接口分离后压测登录会压变味儿有人为了压测方便写了先登录再干正事的脚本可是登录接口每秒钟被调用上千次把登录服务和被压测服务耦合在同一个测试里。这时候登录接口反而成了瓶颈导致业务接口的压测结果严重失真。我的做法压测前先用 CLI 模式单独耗一轮登录接口把获取到的有效 token 存入 CSV 文件压测脚本直接通过 CSV 数据文件读取不同用户的 token跳过登录环节。这样既保证了压测场景的真实性又不会干扰核心被测系统的表现。9. 第八个问题压测过程中 Jmeter 自身成了瓶颈识别方法和规避策略9.1 压测机 CPU 跑满、内存增大不是目标系统的问题有一次我压测一个性能很好的网关接口目标系统负载一直很低但聚合报告的吞吐量就是上不去了CPU 压测机却飙到了 90% 以上。我做了几个排除动作打开 Jmeter 的选项 - 日志查看器看是否有明显的 GC 停顿用jstat -gc pid观察 Jmeter 的 JVM 堆使用情况最后发现是响应数据太大——接口返回一个很大的 JSON 列表Jmeter 的响应数据默认会全部保存在内存里。每一秒 1000 个样本 × 每个样本 2MB 数据内存很快吃满。规避方法在 HTTP 请求取样器里勾选响应数据另存为文件或者去掉将响应结果保存到默认存储在jmeter.properties里设置只保存必要的采样结果字段不要保存Response Data字段开启Summariser定期打印中间汇总用-l生成结果文件而不是把调试输出满屏打印9.2 网络连接数限制你压的不是系统是压测机的端口表如果你在 Linux 压测机上跑高并发最大的瓶颈经常不是 CPU而是端口资源。TCP 客户端连接默认超过 28000 左右时系统会限制新连接报Cannot assign requested address错误。排查和解决的路径# 查看当前临时端口范围 cat /proc/sys/net/ipv4/ip_local_port_range # 临时扩大端口范围 echo 1024 65535 /proc/sys/net/ipv4/ip_local_port_range # 降低 TIME_WAIT 等待时间谨慎使用 echo 30 /proc/sys/net/ipv4/tcp_fin_timeout # 开启端口复用推荐 echo 1 /proc/sys/net/ipv4/tcp_tw_reuse如果你压测的是 HTTP 长连接KeepAlive每个线程占用一个连接端口压力主要来自迭代之间的新连接 vs 复用连接的比例。尽量保持 KeepAlive 开启配合HTTP 请求的资源复用选项能显著减少本机端口占用。9.3 非 GUI 模式的选型理由和实际操作习惯很多人第一次看到jmeter -n -t script.jmx -l result.jtl的时候会问这不就是命令行模式吗对线上压测我强烈建议用非 GUI 模式。非 GUI 模式不吃图形渲染的资源排除了界面刷新对采样的干扰同时能通过-j参数单独写一份日志。我自己的习惯是每次压测都固定成一条命令jmeter -n -t /data/scripts/api_test.jmx -l /data/result/$(date %Y%m%d_%H%M%S).jtl -j /data/logs/$(date %Y%m%d_%H%M%S).log -e -o /data/report/$(date %Y%m%d_%H%M%S)这里有几个值得注意的参数细节-e -o是 jmeter 5.x 的 HTML 报告生成分布图和响应时间图都有省去自己手绘-j和-l分别指定日志与结果文件日志里能看到每个循环的错误数和吞吐量实时进度对超长压测建议在jmeter.properties里设置summariser.interval30每 30 秒打印一行实时摘要10. 最后一个常见误区结果文件越详细越好其实越详细越失真压测结果收集是有代价的。你把每个请求的响应体、请求头、Cookie 全保存下来结果文件写得越细Jmeter 的 I/O 线程越繁忙采样的时间戳就会被推迟。一个常见现象本来 1000 并发结果文件开启完整采样后实际吞吐量掉到 800就是因为 Jmeter 自己写文件的速度跟不上。我建议按需开启字段。在bin/jmeter.properties的jmeter.save.saveservice.*配置里只保留必要的字段jmeter.save.saveservice.output_formatcsv jmeter.save.saveservice.labeltrue jmeter.save.saveservice.response_codetrue jmeter.save.saveservice.response_messagefalse jmeter.save.saveservice.successfultrue jmeter.save.saveservice.threadNametrue jmeter.save.saveservice.timetrue jmeter.save.saveservice.latencytrue jmeter.save.saveservice.bytesfalse jmeter.save.saveservice.sentBytestrue jmeter.save.saveservice.threadCountstrue jmeter.save.saveservice.idleTimetrue jmeter.save.saveservice.hostnametrue jmeter.save.saveservice.response_datafalse jmeter.save.saveservice.samplerDatafalse从压测执行层面就把不必要的数据过滤掉等结果落盘后需要细粒度数据时再根据requestHeaders或responseHeaders选几个抽样日志补测而不是让所有请求都拖着完整 payload 跑。另外对大量采样数据的分析推荐直接用 InfluxDB Grafana 套餐。Jmeter 官方提供了InfluxdbBackendListenerClient只需要在监听器里添加它填上 InfluxDB 地址Jmeter 就会实时把聚合指标推送到时序数据库里。这样边压测边看仪表盘不用等压测结束再慢慢解析 jtl 文件尤其是长稳测试和持续集成流水线里这个组合非常舒服。11. 一点实际经验把所有问题按数据 - 环境 - 脚本三层排查这里再分享一个排查套路。遇到压测异常时先别急着改脚本。我的排查顺序是先看压测机的系统指标——CPU、内存、网络、端口。压测机本身不健康后面的一切分析都没有意义再看 Jmeter 的日志和结果文件确认采样时间戳有没有异常大间隔。如果有大概率是 GC 或写文件阻塞最后才回到脚本逻辑检查断言、参数化、Cookie 管理、变量作用域这三层顺序很多时候能避免对着空气 debug。比如你看到一个接口的响应时间每 30 秒规律性拉长直接猜业务系统有定时任务但如果在第一层发现 Jmeter 每 30 秒在写一次汇总日志你就会先把噪音源排除掉。压测是一个系统工程Jmeter 只是其中一环但这一环的稳定性直接决定你最后的结论可信不可信。我个人踩过最不值当的坑就是在没检查压测机端口范围和堆内存的情况下把一个好好的 1000 并发压测数据当成系统真实能力记录在案。后来复盘才发现压测机连接表满了采样数据后半段全是超时。所以这篇文章里的内容希望能帮你把这些不值当的坑提前绕开。