JMeter 长时间压测出现 OOM,你会用哪些工具定位并解决

解读

面试官想验证三件事:

  1. 你是否真的在生产级压测里遇过“跑一夜就挂”的 OOM,而不是只跑过 5 分钟 demo;
  2. 能否用国内开发/运维环境最常见的命令行+开源工具快速拿到“内存是谁吃了”的直接证据;
  3. 能否把“定位”和“解决”串成闭环:既让当晚的压测先跑完,又能让开发第二天把根因改掉。
    回答时切忌只背“加 -Xmx”或“换 GUI 到非 GUI”,必须体现“采样→分析→调优→回归”四步,并给出可落地的数字指标(如 Old 区 92%、FGC 每 3 min 一次、线程数 5 k 等)。

知识点

  1. JMeter 内存模型:
    – 每个并发线程(Thread Group)= 一个 Java Thread + 一套 SampleResult 列表;
    – 监听器(View Results Tree、Aggregate Report、BackendListener)在内存中累加数据;
    – 长链路断言、大响应体(>2 MB JSON)未关闭,会导致 char[]/byte[] 在 Eden 区无法及时释放。
  2. 国内可落地的采样三件套:
    – jstat -gc -t -h5 pid 1s >> gc.log(无侵入,秒级);
    – jmap -histo:live pid | head -n 30(直看 Top 对象);
    – arthas heapdump --live /tmp/jmeter.hprof(阿里开源,生产可动态 dump,无需重启)。
  3. 解决套路:
    – 治标:非 GUI 模式 + 关闭所有累加监听器 + 打开 -XX:+UseG1GC -Xms4g -Xmx4g -XX:MaxGCPauseMillis=200;
    – 治本:把“写文件”监听器换成 InfluxDBBackendListener + Grafana,压测机只负责发压力,数据走网络;
    – 补充:若断言需解析大 JSON,改用 JSR223 + Gson 流式解析 + 手动 clear。

答案

我上周刚帮支付团队定位一个 12 小时 OOM 问题,思路如下:

  1. 现场止血
    先用 ps -ef | grep ApacheJMeter 拿到 pid,发现 Old 区 98%,FGC 每 150 s 一次。
    立刻用 arthas dashboard 确认线程数 4 800,正常应该 2 000,怀疑监听器堆积。
  2. 采样定位
    a) jstat -gc -t pid 1s 持续 2 min,发现 Old 区上涨斜率 4.2 MB/s,与并发数成正比;
    b) jmap -histo:live | head 显示 org.apache.jmeter.samplers.SampleResult 占 1.7 GB,排第一;
    c) 为防二次 OOM,用 arthas heapdump --live 生成 3.9 GB hprof,拉到本地 MAT,直方图显示 char[] 1.2 GB,GC Root 是 ResultCollector#sampleStore
  3. 根因确认
    脚本里开了“View Results in Table”+“Save Responses to a file”两项,且未勾选“仅保存错误”。12 小时 * 1 200 TPS * 2 KB 响应体 ≈ 100 GB 内存引用,Old 区放不下。
  4. 解决措施
    – 当晚:把 JMeter 切到非 GUI,删除所有可视化监听器,加 -Xms4g -Xmx4g -XX:+UseG1GC -XX:+ExitOnOutOfMemoryError,重启压测,Old 区稳定在 1.2 GB,FGC 降到 1 次/小时;
    – 次日:把 BackendListener 指向内部 InfluxDB,Grafana 模板 ID 1152,压测机内存降到 1.5 GB;
    – 长效:把大响应体断言改成 JSR223 + Jackson Streaming API,并在 tearDown 线程组里手动 sampler.getResponseData().length=0,SampleResult 不再持引用。
    回归验证:同样 12 小时 1 200 TPS 场景,Old 区峰值 1.4 GB,无 FGC,满足 SLA。

拓展思考

  1. 如果压测机是容器(公司 K8s 压测池),记得把 -XX:MaxRAMPercentage=75.0 写进 Dockerfile,防止 cgroup 限制下 JVM 仍按宿主机内存算默认堆;
  2. 对 10 k+ 超高并发,可考虑 JMeter 5.5 的“RedisBackendListenerClient”,把 SampleResult 直接序列化到 Redis Stream,压测机完全无状态;
  3. 若 MAT 发现内存泄漏在第三方插件(如 MQTT、DubboSampler),可临时用 arthas redefine 热替换 class,先让当晚任务跑完,第二天再提 PR 给社区。