JMeter因Groovy脚本频繁创建类导致OutOfMemory问题求助
解决JMeter中Groovy脚本导致的类加载泄漏与OutOfMemory问题
我太懂这个坑了!之前用JMeter 3.x跑高并发测试时也碰到过一模一样的情况——每次执行Groovy脚本都生成新类,类加载数飙到十万+,最后直接OOM。结合你的描述,问题根源很明确:你用evaluate(new File("..."))的方式加载脚本时,Groovy每次都会动态编译生成全新的类定义,而且这些类的ClassLoader因为被JMeter的上下文引用,无法被GC回收,最终导致元空间(Java 8替代永久代的内存区域)溢出。
下面给你几个亲测有效的解决方法,按优先级排序:
1. 直接用JSR223 Sampler的脚本文件引用(最推荐)
这是最简单高效的方案,完全避免动态编译的问题:
- 打开JSR223 Sampler,在Script File输入框中直接填写你的
script.groovy文件路径 - 确保Script Language选择Groovy
- 勾选Cache compiled script if available选项(JMeter 3.1默认带有这个选项,它会缓存编译后的脚本类,整个测试周期只编译一次)
这样JMeter只会加载一次脚本类,不会每次请求都生成新类,类加载数会稳定在一个很低的数值。
2. 复用Groovy的CompiledScript实例(适合必须动态加载的场景)
如果因为业务原因必须动态加载脚本内容,不要每次都调用evaluate,而是提前编译脚本并复用编译后的实例:
// 把这段初始化逻辑放在Test Plan的Setup Thread Group里,或者只执行一次的JSR223 PreProcessor中 import javax.script.ScriptEngineManager import javax.script.CompiledScript // 检查全局变量是否已存在编译后的脚本,避免重复编译 if (!vars.getObject("cachedScript")) { def engineMgr = new ScriptEngineManager() def groovyEngine = engineMgr.getEngineByName("groovy") // 编译脚本文件内容 CompiledScript compiled = groovyEngine.compile(new File("...\\script.groovy").text) // 把编译后的实例存入JMeter全局变量 vars.putObject("cachedScript", compiled) } // 在需要执行脚本的Sampler中,直接调用缓存的实例 def cachedScript = vars.getObject("cachedScript") cachedScript.eval()
这种方式只会在测试启动时编译一次脚本,后续所有请求都复用同一个类,彻底解决重复类加载的问题。
3. 调整JVM参数优化类卸载
如果上面的方法都无法完全解决,你可以通过JVM参数帮助GC回收无用的类加载器:
在JMeter的启动脚本(jmeter.bat/jmeter.sh)中添加以下参数:
-XX:+UseConcMarkSweepGC -XX:+CMSClassUnloadingEnabled -Dgroovy.use.classvalue=true
-XX:+UseConcMarkSweepGC:启用CMS垃圾回收器,它支持元空间的类卸载-XX:+CMSClassUnloadingEnabled:允许CMS回收不再使用的类定义-Dgroovy.use.classvalue=true:Groovy的优化参数,减少类加载时的内存泄漏风险
4. 尽量避免高并发场景下的动态脚本操作
如果你的测试是高QPS或者循环次数极多的场景,尽量减少动态脚本的使用:
- 简单的计算逻辑可以用JMeter内置的函数(比如
__javaScript、__groovy函数,注意后者也要开启缓存) - 复杂逻辑尽量提前封装成Java类,放到JMeter的lib目录下,用JSR223直接调用类方法,而不是动态加载脚本
验证方法
修改完成后,重新启动测试,用Java VisualVM监测已加载类的数量:如果数值不再持续增长,稳定在一个固定区间,说明问题已经解决。
内容的提问来源于stack exchange,提问作者Faliorn
相关产品推荐
相关产品推荐

