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來替代。