Jmeter内存溢出全解析:从OOM类型到性能压测调优实战

Jmeter内存溢出Java heap space性能压测 本内容遵循CC 4.0 BY-SA版权协议
不妧 不妧 墨衍会员 · AI 创作全网分发 墨衍智能分发 通过墨衍一键同步至各平台 可同步平台 CSDN微信公众号微博知乎掘金百家号博客园抖音小红书 你的文章也可以这样分发 墨衍会员 ¥399起/年,写一次发全网 我也要

我先把话说在前面:做性能压测的人,几乎都有过这种经历——脚本跑得好好的,线程数也压上去了,正在盯着聚合报告等数据,突然Jmeter界面卡住不动了,然后控制台蹦出一行熟悉的红字:

TEXT
java.lang.OutOfMemoryError: Java heap space

再严重一点,直接整个进程崩溃,几百万行响应数据没落盘,测试白跑。Jmeter 内存溢出不是冷门问题,反而是用 Jmeter 做压力测试时最容易踩、也最容易被误判的坎。很多人第一反应是“机器内存不够”,然后去加物理内存,结果加到 64G 还是溢出;也有人直接改 jmeter.bat 把堆内存调大,改成 8G、16G,跑一会儿照样爆掉。

这篇东西就是来拆解这件事的。我会把 Jmeter 内存溢出的底层原因、典型触发场景、完整排查逻辑、以及真正能落地的解决办法一次讲清楚。适合正在用 Jmeter 做接口测试、性能压测、或者已经在生产环境上碰过大并发压测崩溃的工程师看。内容偏向实操,尽量让你看完能直接照着处理。

1. 从OutOfMemoryError的报错类型看Jmeter的“死法”

很多人一看到“OutOfMemoryError”就以为是内存不够,然后一股脑调 -Xmx。但 JVM 的内存结构并不只是堆内存一个区域,Jmeter 作为 Java 应用,OOM 也分好几种“死法”,处理方式完全不同。先分清报错类型,再谈解决办法。

1.1 Java heap space:范围最广的堆内存不足

最常见的报错就是 java.lang.OutOfMemoryError: Java heap space。它表示 JVM 堆内存已经被对象占满,且 GC 无法回收出足够的空间给新对象分配。

Jmeter 里谁在吃堆内存?每个线程组里并发线程的上下文对象、采样结果对象、断言产生的临时对象、监听器里累计的报告数据、以及你脚本里显式创建的字符串和集合,全部在堆里。200 个线程并发跑 30 分钟,光采样结果就能累积出几千万个对象。Jmeter 4.x / 5.x 默认的堆参数是 -Xms1g -Xmx1g,很多老版本甚至默认 512MB。在 GUI 模式下开几个监听器,跑不了多长时间堆就满了。

1.2 GC overhead limit exceeded:垃圾回收陷入“死循环”

如果你看到的报错是 java.lang.OutOfMemoryError: GC overhead limit exceeded,那说明堆还没耗尽,但 GC 已经不行了——JVM 花了超过 98% 的时间做垃圾回收,每次回收却释放不了 2% 的堆空间。这种情况下程序基本等于瘫痪,卡顿、无响应、看似死机,实际是 GC 在疯狂运转。

这个报错在 Jmeter 里很典型:脚本写得太“重”,比如对每个响应做正则匹配、把整个响应体存到断言变量里、BeanShell 脚本里循环创建大对象,导致堆里全是短命对象,新生代频繁晋升、老年代快速填满,GC 线程忙不过来,最终触发保护机制。

1.3 Metaspace / native thread:容易被忽略的非堆耗尽

除了堆内存,还有两种 OOM 容易被忽略:

  • java.lang.OutOfMemoryError: Metaspace:元空间装的是类元数据。Jmeter 里如果大量使用 BeanShell 脚本做逻辑,每次执行都会动态生成类,类加载器无法回收,元空间被撑爆。Jmeter 默认的 MaxMetaspaceSize 一般只有 256m,跑复杂脚本很容易中招。
  • java.lang.OutOfMemoryError: unable to create new native thread:这个其实是操作系统线程数限制,不是堆的问题。Jmeter 每个线程组里的线程对应一个 Java 线程,加上 JVM 内部线程,当系统文件句柄数(ulimit)、线程栈空间设置不当,或者线程数本身压得太高,就会报这个错。

三类 OOM 的表现和应对方向差别很大,我用一张表理清楚:

报错信息 实际含义 常见诱因 首要处理方向
Java heap space 堆内存对象过多 监听器累积、响应数据大、脚本大对象 调堆参数 + 脚本瘦身
GC overhead limit exceeded GC 失能,回收率极低 脚本太重、对象生命周期过长 优化脚本、减少短命对象
Metaspace 元空间类加载溢出 BeanShell 大量动态类 换 JSR223 + Groovy、调 MaxMetaspaceSize
unable to create native thread 操作系统线程数达到上限 并发线程数太高、文件句柄限制 降并发、调系统 ulimit

判断一个 OOM 到底属于哪一类,别只靠猜。直接看 Jmeter 控制台或 jmeter.log 里的具体报错信息,再决定下一步动作,否则很容易调错方向。

2. 为什么你的压测脚本会撑爆堆内存:五个典型场景

理解了 OOM 的类型,接下来要回答一个更核心的问题:Jmeter 里的内存究竟被谁吃掉了?我结合自己实际压测中遇到的和帮别人排查过的案例,总结了五个高频触发场景,基本覆盖了 90% 的 Jmeter 内存溢出根因。

2.1 长期开着GUI模式和结果树压大并发

这是最常见、也最冤枉的一种。JMeter 官方文档明确建议不要用 GUI 模式做负载测试,但很多人日常就是这么干的:打开 Jmeter 图形界面,添加线程组,设置 500 并发,然后点绿色启动按钮,一边看结果树,一边等报告。

问题是,GUI 模式下的结果树(View Results Tree)会把每个采样器的请求数据和响应数据全部保存在内存里。500 并发跑 30 分钟,每个请求的响应体就算只有 2KB,累积下来也是几十 GB 的对象。你以为只有 500 个线程在跑,实际上 Jmeter 把所有历史采样结果都“记住”了。这种情况下,堆内存调到 4G、8G 都没用,跑几分钟照样 Java heap space。

还有不少人喜欢把“聚合报告”(Aggregate Report)挂在界面上实时刷新,这也会持续积累统计对象,进一步加速内存消耗。

2.2 超大CSV参数文件与不当的读取方式

接口压测基本都要做参数化,很多人习惯把测试数据全部塞进一个 CSV 文件里,从几万行到几百万行不等。理论上 CSV Data Set Config 是流式按行读取的,不会一次性加载全部数据到内存,但实际使用中有几个隐藏坑:

  • 允许多线程共享同一个 CSV 文件时,Jmeter 的文件句柄和内部迭代器状态会随线程数增长,每个线程都要维护自己的读取指针。
  • 如果 CSV 文件的每条记录里包含很长的字符串(比如几百字节的交易流水号、签名串),多线程并发读取时会频繁创建字符串对象,堆压力会明显上升。
  • 很多人在 CSV Data Set Config 里配置了“Recycle on EOF = True”,测试长时间运行时会反复循环读取文件,同样文档对象持续累积。

在公司环境里经常能看到几百万行历史数据被塞进 CSV,然后 500 线程一启动,还没跑到预期并发量,内存先爆了。

2.3 正则提取器对巨型响应体“贪得无厌”

现在的后端接口返回 JSON 越来越大,动不动几十 KB、几百 KB。很多人在业务流里为了取一个订单号,用正则提取器(Regular Expression Extractor)对整个响应体做全局匹配,贪婪模式(.*)一直匹配到响应结束。

这么做的内存代价相当可观:正则匹配过程中会创建庞大的匹配结果对象和中间字符数组,Jmeter 还需要保留原始响应内容用于后续断言。如果多个采样器都这么写,每个请求的内存开销就从几 KB 膨胀到几 MB。并发一高,内存直接被打满。

2.4 BeanShell脚本在迭代中反复创建对象

BeanShell 是 Jmeter 最老的脚本扩展方式,但它的问题也很突出:每次调用 BeanShell Sampler 或 BeanShell 断言时,Jmeter 都会创建脚本引擎实例并编译脚本,编译产生的类对象不断累积;脚本里如果写了循环填充集合、拼接大字符串这种代码,更是雪上加霜。

我见过一份脚本,在 BeanShell 断言里用了字符串 += 拼接几千次,每一次拼接都会生成新的字符串对象。这种代码跑单线程没问题,一旦上了并发,内存曲线直线上升,最终 GC overhead limit exceeded。

2.5 分布式压测下Controller端汇总压力被低估

很多人在单机压不动之后会转向分布式压测,用一台 Controller 调度多台 Agent(slave)执行测试。但分布式不等于万事大吉:Agent 执行完采样后要把结果回传给 Controller,Controller 端如果开着聚合报告、结果树等监听器,所有 Agent 的原始采样数据照样会堆积在 Controller 的堆内存里。

压测规模越大,Controller 端的汇总压力越大。我见过 4 台 Agent 压 5000 并发,Controller 只有 1G 默认堆,跑到一半 Controller OOM,所有 Agent 直接失联。这种场景里,瓶颈已经从执行机转移到调度汇总端了。

用表格把这五个场景串起来:

典型场景 表现特征 内存根因 解决方向
GUI + 结果树压大并发 跑几分钟就 Java heap space 采样结果全量驻留内存 换 CLI 模式、关闭结果树
超大 CSV 参数化 线程数据量大时内存飙升 字符串对象、文件句柄累积 合理切分文件、JDBC 数据源
正则提取大响应体 单请求内存开销巨大 贪婪匹配产生大对象 用 JSON 提取器、限制匹配范围
BeanShell 脚本逻辑重 GC 频繁、CPU 飙升 动态类、短命对象过多 换 JSR223 + Groovy
分布式 Controller 汇总 Controller 先 OOM 采样结果回传堆积 精简输出、Backend Listener

3. 一次线上压测OOM的完整排查链路

前面讲了场景和根因,这里把一次真实的排查过程完整过一遍。遇到 OOM 不要慌,按这个链路走,基本都能定位到问题。

3.1 现场确认:压测进行到一半卡死

有一次我给一个交易系统做压测,场景是 1000 线程并发、持续 30 分钟。用的就是 Jmeter GUI 模式,开着聚合报告和结果树,CSV 参数文件大约 20 万行。

测试进行到大约 12 分钟的时候,Jmeter 界面开始非常卡,鼠标移动都困难,聚合报告的数据停止刷新。又过了十几秒,控制台打印出:

TEXT
java.lang.OutOfMemoryError: Java heap space

注意一个关键细节:线程组并没有立刻停掉,而是整个 Jmeter 进程陷入半死状态。我先后台确认了 Jmeter 的 PID,再看系统内存,机器上明明还有 32G 可用内存。这就说明问题不是物理内存不够,而是 JVM 堆配置和脚本设计的问题。

3.2 从日志与jstat、jmap定位堆空间占用

我先打开 jmeter.log,看到大量重复的 OOM 堆栈,指向的是结果树保存响应数据的逻辑。然后用 jps -l 找到 Jmeter 进程 ID(假设是 12345),用 jstat -gcutil 12345 1000 观察 GC 情况。

关键信息在 O 这一列(老年代占用率),正常应该在 60%-70% 以下波动。但我看到老年代占用率已经卡在 99.9%,每次 Full GC 只能释放极少量空间,紧接着又涨回去。这说明老年代里堆了大量无法回收的对象。

再用 jmap -heap 12345 看堆配置,确认 Jmeter 当前用的堆上限就是默认的 1G。让我心里有数了:光 GUI 模式下 1000 线程 + 结果树 + 聚合报告累积的采样结果,就远超 1G 堆的容量,这个 OOM 几乎是必然的。

3.3 用MAT/VisualVM定位保留的大对象

理论推断还不够,最好有实锤。我在 Jmeter 启动参数里加上 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/dump,重新跑了一次,这次 OOM 时自动生成了 .hprof 堆转储文件。

用 Eclipse MAT 打开这个 dump,在“Dominator Tree”里可以看到内存占用最大的几个对象。不出所料,排在前面的是两个东西:

  • org.apache.jmeter.visualizers.ViewResultsFullVisualizer$SamplingResultHolder,结果树持有的采样结果对象。
  • org.apache.jmeter.save.SaveService 相关的响应文本缓存。

这两个对象占了整个堆的 70% 以上。到这里根因就非常清楚了:监听器设计不合理,堆配置也不足,两个原因叠加导致 OOM。

3.4 得出结论:脚本设计与运行模式的双重错误

最终结论不是“机器内存不够”,而是:

  1. GUI 模式 + 结果树把每个请求的完整响应保存在内存里,1000 线程 12 分钟产生了海量采样对象。
  2. 默认堆内存只有 1G,远不足以支撑这种模式下的对象堆积。
  3. 聚合报告虽然界面看着轻巧,实际也在持续累积统计对象。

順带说一句,如果你不想装 MAT,也可以用 JDK 自带的 jvisualvm 打开 hprof 文件,同样能看到大对象分布。MAT 的支配树视图更清晰,推荐优先用。

排错时最好养成个习惯:Jmeter 启动参数里常驻加上 -XX:+HeapDumpOnOutOfMemoryError。这样只要 OOM,就会自动落一个堆转储文件,省得事后靠猜。

4. 解决与预防:从JVM调优到脚本瘦身的完整方案

排查清楚之后,剩下的就是执行层面的优化。我按照从粗到细的顺序给出完整的解决路径,每一步都很具体,可以直接抄作业。

4.1 第一步:调整堆内存参数

Jmeter 调堆内存的常规做法是修改 bin 目录下的启动脚本,或者创建独立的 setenv 文件,按操作系统区分:

Windows 环境:在 Jmeter 的 bin 目录下,可以看到 jmeter.bat。用文本编辑器打开,找到类似下面的行:

TEXT
set HEAP=-Xms1g -Xmx1g -XX:MaxMetaspaceSize=256m

直接修改成你需要的大小,比如:

TEXT
set HEAP=-Xms4g -Xmx4g -XX:MaxMetaspaceSize=512m

Linux / macOS 环境:修改 jmeter 脚本或者直接创建 setenv.sh(放在 bin 目录下,Jmeter 启动时会自动加载),写入:

BASH
export HEAP="-Xms4g -Xmx4g -XX:MaxMetaspaceSize=512m"

如果你用的版本没有显式 HEAP 变量,也可以通过 JVM_ARGS 传参:

BASH
export JVM_ARGS="-Xms4g -Xmx4g -XX:MaxMetaspaceSize=512m"

有几个经验供你参考:

  • -Xms 和 -Xmx 建议设置成一样大。这样 JVM 启动时就申请好全部堆内存,运行时不用动态扩容,减少扩容带来的性能抖动。我以前习惯只设 -Xmx,结果压测高峰时 JVM 频繁扩容,Full GC 时间明显增加,后来改成相等设置,稳定了很多。
  • 堆不是越大越好。过大的堆会导致单次 Full GC 时间很长,压测线程卡顿,甚至让 GC 停顿超过接口超时时间。一般压测机如果只有 8G 内存,堆设置 4G 左右就够;
  • Jmeter 本身是 Java 8+ 运行时的话,可以追加 GC 策略参数:
TEXT
- XX:+UseG1GC -XX:MaxGCPauseMillis=200

G1 收集器对大堆的停顿控制明显好于默认的 Parallel GC。这个参数我实测在长时间压测中很有帮助,GC 引起的尖刺少了很多。

4.2 第二步:切换到命令行非GUI模式

调完堆内存只是第一步,更重要的动作是放弃 GUI 模式做压测。Jmeter 命令行模式(Non-GUI mode)的内存占用比 GUI 模式少一个数量级,因为它不需要渲染界面,也不会因为结果树等组件而保留采样对象。

命令行压测的基本命令:

BASH
jmeter -n -t test_script.jmx -l result.jtl -e -o /data/html_report
  • -n:非 GUI 模式
  • -t:指定测试计划文件
  • -l:输出采样结果文件(JTL)
  • -e:测试结束后生成 HTML 报告
  • -o:指定 HTML 报告输出目录

这里有个关键点:JTL 文件本身也会随着压测推进不断变大,如果测试时长很长、并发很高,JTL 文件会占用磁盘空间,但不会占用 Jmeter 的 JVM 堆,所以这个问题不大。真正要注意的是,HTML 报告生成时 Jmeter 会读取并解析 JTL 文件,如果 JTL 有几十 GB,生成报告这一步也会内存紧张,这种情况可以用 -g result.jtl -o report_dir 单独在一台有足够内存的机器上生成报告,不要在压测机上干这个活。

为什么说 GUI 模式做压测是“原罪”:Jmeter 汇总报告里的每个采样结果、每个响应体都要进内存,在 GUI 模式下这些数据还被渲染到界面上,双重消耗。而在 CLI 模式下,Jmeter 只写文件,不保留对象,内存占用会显著下降。

4.3 第三步:监听器瘦身与结果收集策略

即使切到 CLI 模式,测试计划里已有的监听器依然会被执行,只是不再产生界面。为了进一步降低内存消耗,建议压测计划里只保留必要的监听器。

可以做减法的地方:

  • 结果树(View Results Tree)只在调试脚本阶段使用,压测时务必禁用或删除。
  • 聚合报告(Aggregate Report)和用表格查看结果(Summary Report)在 CLI 模式下都是多余的,因为 JTL 文件已经包含了全部采样数据,事后想怎么算都行。
  • 如果你的 JTL 文件只是用来统计吞吐率和响应时间,可以在测试计划里加一个 Simple Data Writer,然后只勾选必要的字段:时间戳、线程组、采样器名、响应时间、状态码、成功标志。不勾选响应体、请求体、Cookie、Headers 这些字段,JTL 文件体积能缩小 80% 以上,内存和磁盘压力都小很多。

另外,如果你不想维护 JTL 文件,也不想事后生成 HTML 报告,可以改用 Backend Listener 直接把实时指标发送到 InfluxDB 时序数据库,再用 Grafana 展示。这样 Jmeter 本地几乎不保留采样对象,内存开销被压到最低。对于长时间压测(比如跑 8 小时、12 小时稳定性测试)来说,这条路是最推荐的。

4.4 第四步:CSV数据集、响应体提取、脚本语言的优化要点

这一节针对脚本本身的细节优化,每一个点都是我在实际项目中踩过的坑。

CSV 参数化优化

如果 CSV 文件几十万行、甚至几百万行,不要一把梭。建议:

  • 把大 CSV 按业务线切分成多个小文件,分别用多个 CSV Data Set Config 加载,控制每个文件的规模。
  • 如果单行数据本身很长,比如包含几百字节的 Base64 串,考虑只保留必要字段,减少字符串对象体积。
  • CSV Data Set Config 的“Sharing Mode”注意设置。只在一个线程组内共享,就选 Current thread group;各线程之间完全不共享,就选 All threads。选错了可能导致多线程重复读取同一个值,也容易让内部状态对象累积。
  • 如果数据量太大、业务数据又需要灵活查询,可以直接用 JDBC Request 从数据库读取参数,每次只取当前批次的数据,而不是一次性把全量数据拉进 Jmeter。

响应体和提取策略优化

  • 能用 JSON Extractor 就别用正则表达式。JSON 提取器走的路径更高效,占用的中间对象远比正则匹配少。
  • 如果非要写正则,避免贪婪模式。比如提取订单号,用 "orderId":"([^"]+)" 就比 "orderId":"(.*)" 安全得多,后者会一直匹配到行尾,容易把整个响应体放进捕获组。
  • 断言时尽量用 JSON Assertion 或者 Response Assertion 的关键字匹配,不要勾选“Ignore status”然后对完整响应体做 contains 匹配,那样 Jmeter 必须把完整响应体保存在内存里。

脚本语言优化:从BeanShell到JSR223 + Groovy

BeanShell 在 Jmeter 里就像“能用但别常用”的工具:每次执行都动态编译,类对象累积在元空间,脚本里创建的对象生命周期也不可控。所以凡是涉及复杂逻辑的场景,我统一推荐 JSR223 Sampler + Groovy。

原因也很简单:JSR223 组件支持脚本编译缓存,勾选“Cache compiled script if available”之后,Groovy 脚本只编译一次,后续所有线程复用同一个编译结果,不会反复产生临时类。而 BeanShell 没有这个机制。

一个典型的 JSR223 断言脚本长这样:

GROOVY
def json = new groovy.json.JsonSlurper().parseText(prev.getResponseDataAsString())
if (json.code != 200) {
AssertionResult.setFailure(true)
AssertionResult.setFailureMessage("业务code不正确: " + json.code)
}

用 groovy.json.JsonSlurper 解析 JSON 比正则提取高效得多,内存占用也少。注意 Groovy 脚本里不要写那种几千次循环拼字符串的逻辑,能避免就避免。

4.5 第五步:分布式压测的扩展与降载思路

如果单台压测机的 CPU、内存已经跑满,单机调优解决不了扩容问题,这时候才需要上分布式压测。但分布式压测不是为了“省内存”,而是为了“分摊负载”。

分布式压测的基本用法:

  • 各执行机(Agent)先启动 jmeter-server,等待 Controller 调度。
  • Controller 机器上用 -r 参数远程启动所有 Agent:jmeter -n -t test_plan.jmx -l result.jtl -r
  • Controller 只负责下发测试计划、收集汇总结果,不参与实际发压。

避坑要点:

  1. Controller 端同样不能开结果树之类的监听器,否则所有 Agent 的采样结果照样灌爆 Controller 堆。
  2. 如果 Controller 端生成 JTL 文件导致本地 IO 和内存吃紧,可以配置 Agent 端直接写入各自本地磁盘,测试完后再收集合并。用 -R ip:port 指定远程 Agent 时,配合 -Jremote_hosts 等属性做结果分开存储,能显著减轻 Controller 压力。
  3. 如果压测规模很大(几千并发以上),最好给 Controller 也按上文方法设置 4G 以上的堆内存,否则 Controller 先倒下,Agent 全成孤儿。

分布式压测还有一个隐藏问题:多台 Agent 的时钟必须保持一致,否则结果汇总后时间轴错位,报告没法看。我建议所有压测机统一配置 NTP 同步。

5. 调优后的验证手段与日常压测检查清单

方案落地之后,最怕的是“改了也不知道有没有效果”。我自己会做几件验证性的事,确保每次调整都心里有底。

5.1 怎么确认参数真正生效

改完 jmeter.bat 或 setenv.sh 之后,启动 Jmeter,然后立刻看 JVM 的实际参数是否生效。

命令行模式下,执行:

BASH
jmeter -v

或者直接启动后,用 jps -l 找到进程,再用:

BASH
jmap -heap <pid>

看输出的 MaxHeapSize 是否等于你设置的 -Xmx 值。这个方法最直接,能避免“改了配置但没重启 / 文件改错位置”这种低级错误。

更直观的做法是在 Jmeter 界面里用“Run”菜单的“Start”跑一个最简单的 1 线程 1 请求的测试,同时观察任务管理器或 top 里 Jmeter 进程的初始内存占用,如果初始就占用了几个 G,说明 -Xms 生效了。

5.2 一套我自己常用的压测前检查清单

与其每次 OOM 后再去排查,不如跑压测前按清单过一遍,很多问题其实能提前避免。

检查项 正确做法 常见错误
运行模式 CLI 模式(-n -t -l -e -o) GUI 模式下直接压测
堆内存配置 按机器内存 50%-60% 设置 -Xmx,-Xms 等于 -Xmx 用默认 1G 裸奔
监听器 只保留必要字段的 Simple Data Writer / Backend Listener 开着结果树、聚合报告跑压测
CSV 数据 控制文件大小,按需读取,必要时走 JDBC 几百万行全量读入
断言 用 JSON 断言、JSR223 + Groovy BeanShell 里做复杂循环和字符串拼接
响应体提取 用 JSON Extractor / 非贪婪正则 正则贪婪匹配整个响应体
分布式 Controller 也调大堆,结果分散存储 Agent 数量加了,Controller 不管
GC 日志 启动参数加 GC 日志和 HeapDump 出问题后无据可查

除了表格这些,还有一点容易被忽略:压测脚本里尽量不要加“思考时间 + 循环次数”这种高频小循环组合,比如每个线程循环 100 万次,每次循环之间只间隔 10ms。循环本身不会直接造成 OOM,但循环里如果触发了断言、写日志、维护变量,对象累积速度会非常惊人。

5.3 长时间运行容易忽视的“内存缓慢增长”问题

有一种情况不是突然 OOM,而是压测跑了几个小时,内存占用慢慢爬升,最后在某一个时间点崩掉。这种“慢泄漏”很多时候出在 JSR223 脚本里:脚本从上下文取变量、往某个静态 Map 里写数据、或者没关闭流资源,对象只进不出。

排查慢泄漏,最有效的手段是 GC 日志。Jmeter 启动参数里加上:

TEXT
- Xlog:gc*:/data/jmeter-gc.log

JDK 8 及更早版本用:

TEXT
- verbose:gc -Xloggc:/data/jmeter-gc.log -XX:+PrintGCDetails

压测跑一段时间后,打开 GC 日志看老年代占用曲线。如果老年代占用率持续缓慢上升、Full GC 后也降不回初值,说明有对象一直“逃逸”在 GC 根路径上,基本可以断定脚本里有全局引用或类加载器泄漏。这时候再用 OOM 时自动生成的 dump 文件定位具体对象类型,比在代码里盲猜靠谱得多。

关于慢泄漏,我再多说一句:Jmeter 自身的线程组变量、属性变量(props)会后续迭代保留,脚本里如果不停地往 props 里塞数据,也会造成内存只增不减。跨线程传数据可以用 vars(每个线程独立、迭代结束会清理),尽量少用 props(全局可见、长期驻留)。

写在最后的一点个人体会

搞 Jmeter 内存溢出这个问题,说实话没有太多玄学。我踩过最深的坑,是早期一味地调大堆内存,结果从 1G 调到 8G,问题只是晚点出现,并没有消失。后来才意识到,堆内存大小只是兜底策略,真正要解决的是“Jmeter 到底在内存里留了什么东西”。一个只做请求的 Jmeter,内存开销很小;一个把结果树、聚合报告、完整响应体、BeanShell 编译类全堆在内存里的 Jmeter,给多大堆都不够用。

所以我现在的习惯是:调试阶段随便用 GUI,正式压测一定走 CLI;验证脚本时开结果树,压测前全部禁用;能用 JSR223 + Groovy 就绝不用 BeanShell;能在测试计划里少保存一个字段,就绝不多保存一个字段。这套做法帮我扛过了很多长时间、高并发的压测场景,希望对你也有用。如果你手头的脚本还在频繁 OOM,先别急着加内存,按上面的链路走一遍,大概率能找到比你预想中简单得多的根因。

相关推荐

基于Python+Open CV的手势识别算法设计

伴随着人工智能时代的到来,人机交互的领域也逐渐成为研究的一大方向;其中,手势识别是人机交互领域的一项关键技术,自此手势识别也迎来了一波高潮,近几年无论是在消费领域、电子领域、数码领域、家电领域甚至汽车领域上,都能或多或少的见到手势识别的身影。 手势识别在设计智能高效的人机界面方面具有至关重要的作用, 目前手势识别已应用到手语识别、智能监控、到虚拟现实等各个领域,手势识别的原理都是利用各种传感器(例如红外、摄像头等)对手部的形态进行捕捉并进行建模,形成一个模型信息的序列帧,而后将这些信息序列转换为机器能够识别的相对应指令(例如打开、切换菜单、移动等)来完成控制。 采用Python的集成开发环境Pycharm进行本次课程设计,在Pycharm中进行需要库(模块)的下载,调取电脑摄像头,按帧读取摄像头采集到的头像,形态学处理,图像旋转(由于摄像头采集到的图像是镜像,需要用cv2.flip将摄像头采集到的图像进行处理),选取图片中固定位置作为手势输入,用红线画出手势识别框,基于hsv的肤色检测,进行高斯滤波,找出轮廓,求出图像中手势的凹凸点,手指间角度求取。

努力学习前端的小陈

ROS-CV-ImageConverter:将ROS图像转换为OpenCV(cv :: Mat)类型的图像

ROS-CV图像转换器 依存关系 cv_bridge(ROS软件包)

Willis Wang

计算机视觉图像处理-CV2与图像插值算法

用最近邻插值与双线性插值原理完成图像缩放 最近邻插值,是指将目标图像中的点,对应到源图像中后,找到最相邻的整数点,作为插值后的输。缺点:使用最近邻插值算法处理图像放大时,在图象中可能出现明显的块状效应 双线性插值,在两个方向上做三次线性插值。使用双线性插值算法处理图像放大时,在图象中可能出现马赛克 代码 import cv2 img = cv2.imread(’./xiabang.jpg’, cv2.IMREAD_UNCHANGED) #缩放比例 scale_percent = 0.3 width = int(img.shape[1] * scale_percent) height = int

weixin_38697274

基于水平集CV模型的图像分割(Matlab代码)

基于水平集CV模型的图像分割(Matlab代码),全套代码

小宇QAQ

导向滤波(guide_filter)的python实现

导向滤波(guide_filter)的python实现,可以结合暗通道;理论实现比较好的图像去噪效果。

zachzhan_g
返回顶部
不妧
博客等级 码龄7年 43粉丝 2,640原创