apache jmeter 2.13(以下简称jmeter2)版本后,2.x系列就作古了。前些日子,apache jmeter 3.0(以下简称jmeter3)版本正式发布,新生的事物,功能肯定强大了很多,但作为开源产品,稳定性自然要打些折扣,一位同学前几天在使用jmeter3时不幸中招。
原本好用的jdbc请求脚本,压测数据库,使用jmeter3版本时直接oom,回退至jmeter2,一切正常,啥也别说,肯定是又出bug了,本着求实的精神,咱来看看jmeter3为毛oom了。
<a></a>
首先在jmeter3中配置输出heapdump文件,在jmeter.sh中增加以下一行:
<code>jvm_args="-xms512m -xmx512m -xx:+heapdumponoutofmemoryerror -xx:heapdumppath=~/gc.hprof</code>
然后启动jmeter程序,观察jmeter的jvm内存变化,再次oom了,把输出的gc.hprof文件,利用memory analyzer来分析,得到一堆index文件如下图:
利用eclipse的mat插件,加载全部的index文件,查看哪个对象占用了内存,如下图:
看到abstractjdbctestelement类中的perconncache对象,占用了441m的内存,确定是这个对象内存泄露了。
先来看看这个perconncache究竟是做什么用的
这是一个hashmap,用来缓存每个连接(即connection,下文简称为conn)的preparedstatements。而笔者的压测脚本中,最大conn数设定的是10个,就是说,perconncache的size最大应该是10才对,ok,估计问题就出在这里,笔者猜测应该是每次请求的conn都具有不同的hashcode。
为了验证这个猜测,笔者用远程debug来查看一下,每次获取conn时的hashcode。
debug后发现,在jmeter3中有一个有趣的现象,每次获取到的conn果然具有不同的hashcode,但是jmeter3与db的conn总数却刚好是10个,就是说——物理连接的总数量并没有变,但每次从连接池获取到的connection对象却一直在变。
笔者又debug了一下jmeter2,jmeter2的结果是符合预期的,物理连接的总数是10个,perconncache的size也是10,每次从连接池中获取的conn也都能在perconncache中命中。
不论是jmeter2还是jmeter3,jdbcsampler的实现都是一样的:
如图所示,这里有3个关键操作,获取conn,执行sql,关闭conn。在执行sql的步骤中,perconncache会进行缓存验证,如果没有命中,则把新的conn缓存。
每次getconnection()都是连接池的操作,所以,问题就应该出在jmeter3使用的连接池实现上。
下面来解释一下,为什么jmeter3的连接池就会出现oom的问题。
因为jmeter3使用的是dbcp2,在getconnection()方法中,每次不是直接返回connection对象,而是都会新new一个poolguardconnectionwrapper对象,看下面的代码:
可以看到,conn并没有变,但是每次取到的对象都是重新new的包裹类poolguardconnectionwrapper,每次都是新的对象,hashcode当然就不一样了,在hashmap无法命中,所以最终每一次getgetconnection()时,perconncache都产生一个新成员,最终oom。
为啥jmeter2没有这个问题呢?jmeter2使用的是excalibur-datasource,每次connection取到的就是com.mysql.jdbc.jdbc4connection对象本身,自然在perconncache里面就可以命中了
方式一:抛弃dbcp2,回退到excalibur-datasource,问题解决
方式二:修改perconncache缓存机制,不是用connection作为key,而是利用sql的内容作为key,当然也可以直接删除这个cache。
笔者尝试去掉了缓存机制,没有再发生oom问题,压测结果对整体的tps影响也不大
jmeter3稳定性还有待考验,近期不推荐使用,建议使用jmeter2.13来替代。