JMeter压测中的BeanShell内存泄露

1、前言

使用JMeter进行性能测试,有时候需要编写程序辅助,主要的编程方式主要有BeanShell、JSR223脚本(Groovy,Javascript)。这里对BeanShell、Groovy编程的性能测试过程中出现的问题和解决方法进行探讨。本文结合实际项目-车联网的性能测试场景进行说明。

2、车联网背景

车载终端设备(OBD)是一款具备通行功能的移动设备,安装在汽车上,实时采集汽车行驶过程中的各项数据,并通过通信模块发送给后台,后台的计算平台会对汽车的各项数据进行计算(油耗、里程、驾驶行为分析等)。OBD设备发送给后台的数据包主要包含:时间点、经纬度、速度、当前油耗、当前里程。

3、性能测试场景

目标:N个车载终端设备给后台发送多段行驶里程数据,后台计算的性能瓶颈。OBD通过TCP协议发送汽车行驶过程的数据包,所以一段行程里程中,包含有N个数据包,而且每个数据包的时间应该为线性增长的,而且其经纬度、速度、油耗均需要动态变化。那么进行性能测试时,时间、经纬度、速度、油耗、里程这些都需要通过编程实现。

4、BeanShell编程

使用BeanShellPreProcessor组件

根据规范的报文格式,传入时间点、经纬度、里程、油耗等,调用java程序合成16进制的传输报文。

主Sampler- “Java请求-报文合成”,为一个java程序,负责根据传入的参数,合成16进制的可供OBD设备使用的传输报文。

处理GPS包报文的BeanShell

主要生成时间点、经纬度数据:主要逻辑已经封装到JAVA程序,BeanShell为最低限度(最少编程)的程序调用。


处理CAN包报文的BeanShell

主要生成时间点、速度、里程、油耗数据:主要逻辑已经封装到JAVA程序,BeanShell为最低限度(最少编程)的程序调用。


压力测试

100个线程施压,4核CPU,8G内存,JVM配置的最大堆3.6G,JVM配置线程栈256K。

压力机本身系统监控

可以看到,7分钟内已经把3.6G的堆内存全部吃完,最后OOM内存耗尽系统崩溃。


JVM内存分析

BeanShell内部对象占用的堆内存超过了50%。



结论

压力线程开始阶段工作正常,后阶段由于内存不足,频繁full GC导致阻塞时间加长;

内存占用非常高,很短时间就把内存消耗殆尽,30分钟内程序崩溃;

JMeter这种使用BeanShell的方式,存在严重的内存泄露;

此种常规的压力测式方法,不能形成有效的压力。


5、Groovy编程

JMeter官网推荐使用JSR223脚本进行替代BeanShell。使用Groovy脚本可拥有类编译和编译缓存的优势。

使用JSR223 PreProcessor组件


处理报文的Groovy程序

程序逻辑与BeanShell版本一致,只是换成groovy语言改写。

压力测试

100个线程施压,4核CPU,8G内存,JVM配置的最大堆3.6G,JVM配置线程栈256K,持续30分钟。

压力机本身系统监控

可以看出:

堆内存占用大幅降低(对比BeanShell版本),已使用堆最高为2.4G,为可使用3.6G堆的2/3或66.7%。

类装载曲线持续陡峭,说明Groovy相关类在测试期间持续加载。


压力线程监控

压测线程大部分时间都不干活,而处于阻塞和睡眠状态中。


压测线程堆栈分析

对JVM进行heap dump后,对其线程堆栈分析。发现阻塞点在类加载。

java.lang.ClassLoader.loadClass为什么会导致阻塞?我们来看看其源码就明白了,加载类的方法有一个synchronized同步块,需要线程竞争锁。



结论

内存占用在合理范围内;

需要持续加载类;

压力测试线程工作不合理,大部分时间处于由于类加载引发的阻塞;

由于大量线程阻塞,吞吐率很低;

在JMeter中,不适合使用groovy编程进行压力测试。


6、BeanShell编程再探

由于BeanShell的Interpreter存在内存泄露,常规方法无法支持长时间的压力测试。JMeter官网推荐,在使用BeanShell进行长时间测试时,打开选项“Reset bsh.Interpreterbefore each call”,则在每次调用BeanShell程序前,都把解释器重置,以释放解释器之前占用的内存。

6.1、重置选项的BeanShell

BeanShellPreProcessor


压力测试

100个线程施压,4核CPU,8G内存,JVM配置的最大堆3.6G,JVM配置线程栈256K,持续30分钟。

压力机本身系统监控

内存使用量较少,已使用堆<1G,已分配堆<1.25G;

类加载曲线平稳,没有出现抖动;


压力线程监控

从开始到结束,线程都阻塞严重。


阻塞分析

由于解释器重置,所以每次都需要重新加载类,线程阻塞点仍然是类加载:


JVM使用CPU分析

CPU时间主力:

bsh.classpath.ClassManagerImpl.classForName()方法:类加载,占比48.8%;

bsh.BshClassManager.plainClassForName方法:类加载相关,占比41.2%;

BinaryTCPClient4OBD.read()方法:报文TCP数据包发送给后台后等待响应,占比6.7%。


压测吞吐率分析

吞吐率表现尚可,TPS一直稳定在200左右,最终TPS=196.5/s。


结论

内存占用相对较低,30分钟内程序不会OOM崩溃;

每次调用BeanShell前重置解释器,从而每次解释脚本均需要重新尝试加载类,导致线程大量阻塞;

重置解释器,释放了内存,但阻塞了线程,导致吞吐率无法提高;

性能比groovy版本好,在压力测试持续30分钟,总吞吐率在200/s左右的情况下,是一个可选的方案。

6.2、改进的重置选项BeanShell

有没有一种既能通过重置解释器来释放内存,而线程阻塞时间大大缩短的鱼和熊掌兼得?如果实现的话,内存降下来,线程阻塞减少,吞吐率升高,那就^_^太好了^_^。

Idea: 固定重置,改为按需重置。

为此,需要一点微创新。

微创新

修改JMeter源代码

修改org.apache.jmeter.util包下的BeanShellTestElement类:加入一个按需重置标识。


按需重置

JMeter脚本中,加入计数器,用来计算迭代次数:


到达一定的迭代次数,触发重置:


压力测试

100个线程施压,4核CPU,8G内存,JVM配置的最大堆3.6G,JVM配置线程栈256K,持续30分钟。

压力机本身系统监控

内存使用率较高60%-80%,内存通过GC后回收顺利,占用率仍在合理范围;

类加载曲线平稳。


压力线程监控

进入reset阶段,线程阻塞时间变长;reset结束,类加载完成后,线程阻塞时间减少;

随着运行时间的增长,阻塞的频率升高。



JVM使用CPU分析

CPU时间主力:

BinaryTCPClient4OBD.read()方法:报文TCP数据包发送给后台后等待响应,占比69.1%,为原始重置版本的10倍;

bsh.classpath.ClassManagerImpl.classForName()方法:类加载,占比16.5%;

bsh.BshClassManager.plainClassForName方法:类加载相关,占比12.9%;


压测吞吐率分析

平时TPS在450+,reset阶段TPS在350左右,总TPS=462.5/s;

吞吐率是原始重置版本的2.35倍。


结论

按需重置版本,内存占用较高,但处于合理范围内;

按需重置版本,CPU的使用效率大幅增加,从而形成有效压力;

按需重置版本,测试吞吐率倍增;

当前的按需重置版本,随着时间的增长,线程的阻塞频率升高,按需重置的算法有改进空间。


7、BeanShell内存泄露分析

官方解释

Each BeanShelltest element has its own copy of the interpreter (for each thread). If the testelement is repeatedly called, e.g. within a loop, then the interpreter isretained between invocations unless the "Reset bsh.Interpreter before eachcall" option is selected.

Somelong-running tests may cause the interpreter to use lots of memory; if this is the case try using the reset option.[reference 2]

技术分析

JMeter中的请求组件(如HTTPSampler)对象实例,在循环执行过程中,并不是每次都创建一个新的对象来执行,实际上只会创建一次然后重复使用。

JMeter使用XStream技术从JMX文件反序列化得到各组件实例,在启动测试线程时,clone一份给测试线程,测试线程使用过程中不会创建请求组件对象。

请求组件引用栈:HTTPSamper -》BeanShellPrePrecessor –》BeanShellInterpreter –》 bsh.Interpreter–》 NameSpace, BshClassManager, Parser. 其中,BeanShell执行过程中的变量、类等都存放在全局的NameSpace对象(内部数据结构为HashTable)里。

内存泄露应该发生在NameSpace中:


单线程调试JMeter,发现NameSpace中的methods和classManager.listeners发生内存泄露:



8、总结

Groovy编程进行性能测试,压力线程阻塞严重,CPU使用效率低,效果不佳。

BeanShell编程进行性能测试,不带重置选项,可能有严重的内存泄露,短时间内即把内存消耗殆尽,会导致OOM,引发系统崩溃。

BeanShell编程进行性能测试,带重置选择,压力线程阻塞十分严重,CPU使用效率不高,吞吐率不高,但可长时间运行,适合进行稳定性测试。

BeanShell编程进行性能测试,按需重置Interpreter,阻塞、CPU效率、吞吐率等,表现最佳,性能最好。

复杂BeanShell脚本,长时间循环运行,可能引发内存泄露而系统崩溃。解决方法为:使用重置选项,或优化脚本代码。

压力测试,BeanShell程序应该去除不必要中间结果、变量赋值,不引用其他脚本,尽可能的短小精悍。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容