Java程序性能測(cè)試1 概述在開(kāi)發(fā)中,性能測(cè)試是設(shè)計(jì)初期容易忽略的問(wèn)題,開(kāi)發(fā)人員會(huì)為了解決一個(gè)問(wèn)題而“不擇手段”,作者所參與的項(xiàng)目中也遇到了類似問(wèn)題,字符串拼接、大量的網(wǎng)絡(luò)調(diào)用和數(shù)據(jù)庫(kù)訪問(wèn)等等都對(duì)系統(tǒng)的性能產(chǎn)生了影響,可是大家不會(huì)關(guān)心這些問(wèn)題,“CPU速度在變快”,“內(nèi)存在變大”,并且,“好像也沒(méi)有那么慢吧”。 有很多商業(yè)的性能測(cè)試軟件可供使用,如Jprofiler、JProbe Profiler等,但在開(kāi)發(fā)當(dāng)中顯得有些遙遠(yuǎn)而又昂貴。 2 目標(biāo)本文將講述如何利用Java語(yǔ)言本身提供的方法在開(kāi)發(fā)中進(jìn)行性能測(cè)試,找到系統(tǒng)瓶頸,進(jìn)而改進(jìn)設(shè)計(jì);并且在盡量不修改測(cè)試對(duì)象的情況下進(jìn)行測(cè)試。 3 預(yù)備知識(shí)面向?qū)ο缶幊掏ㄟ^(guò)抽象繼承采用模塊化的思想來(lái)求解問(wèn)題域,但是模塊化不能很好的解決所有問(wèn)題。有時(shí),這些問(wèn)題可能在多個(gè)模塊中都出現(xiàn),像日志功能,為了記錄每個(gè)方法進(jìn)入和離開(kāi)時(shí)的信息,你不得不在每個(gè)方法里添加log("in some method")等信息。如何解決這類問(wèn)題呢?將這些解決問(wèn)題的功能點(diǎn)散落在多個(gè)模塊中會(huì)使冗余增大,并且當(dāng)很多個(gè)功能點(diǎn)出現(xiàn)在一個(gè)模塊中時(shí),代碼變的很難維護(hù)。因此,AOP(Aspect Oriented Programming)應(yīng)運(yùn)而生。如果說(shuō)OOP(Aobject Oriented Programming)關(guān)注的是一個(gè)類的垂直結(jié)構(gòu),那么AOP是從水平角度來(lái)看待問(wèn)題。 動(dòng)態(tài)代理類可以在運(yùn)行時(shí)實(shí)現(xiàn)若干接口,每一個(gè)動(dòng)態(tài)代理類都有一個(gè)Invocation handler對(duì)象與之對(duì)應(yīng),這個(gè)對(duì)象實(shí)現(xiàn)了InvocationHandler接口,通過(guò)動(dòng)態(tài)代理的接口對(duì)動(dòng)態(tài)代理對(duì)象的方法調(diào)用會(huì)轉(zhuǎn)而會(huì)調(diào)用Invocation handler對(duì)象的invoke方法,通過(guò)動(dòng)態(tài)代理實(shí)例、方法對(duì)象和參數(shù)對(duì)象可以執(zhí)行調(diào)用并返回結(jié)果。 說(shuō)到AOP,大家首先會(huì)想到的是日志記錄、權(quán)限檢查和事務(wù)管理,是的,AOP是解決這些問(wèn)題的好辦法。本文根據(jù)AOP的思想,通過(guò)動(dòng)態(tài)代理來(lái)解決一類新的問(wèn)題——性能測(cè)試(performance testing)。 性能測(cè)試主要包括以下幾個(gè)方面: l 計(jì)算性能:可能是人們首先關(guān)心的,簡(jiǎn)單的說(shuō)就是執(zhí)行一段代碼所用的時(shí)間 l 內(nèi)存消耗:程序運(yùn)行所占用的內(nèi)存大小 l 啟動(dòng)時(shí)間:從你啟動(dòng)程序到程序正常運(yùn)行的時(shí)間 l 可伸縮性(scalability) l 用戶察覺(jué)性能(perceived performance):不是程序?qū)嶋H運(yùn)行有多快,而是用戶感覺(jué)程序運(yùn)行有多快. 本文主要給出了計(jì)算性能測(cè)試和內(nèi)存消耗測(cè)試的可行辦法。 4 計(jì)算性能測(cè)試4.1 目標(biāo):通過(guò)該測(cè)試可以得到一個(gè)方法執(zhí)行需要的時(shí)間 4.2實(shí)現(xiàn):Java為我們提供了System. currentTimeMillis()方法,可以得到毫秒級(jí)的當(dāng)前時(shí)間,我們?cè)谝郧暗某绦虍?dāng)中一定也寫(xiě)過(guò)類似的代碼來(lái)計(jì)算執(zhí)行某一段代碼所消耗的時(shí)間。
但是,在每個(gè)方法里面都寫(xiě)上這么一段代碼是一件很枯燥的事情,我們通過(guò)Java的java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler利用動(dòng)態(tài)代理來(lái)很好的解決上面的問(wèn)題。 我們要測(cè)試的例子是java.util.LinkedList和java.util.ArrayList的get(int index)方法,顯然ArrayList要比LinkedList高效,因?yàn)榍罢呤请S機(jī)訪問(wèn),而后者需要順序訪問(wèn)。 首先我們創(chuàng)建一個(gè)接口
然后我們創(chuàng)建測(cè)試對(duì)象實(shí)現(xiàn)這個(gè)接口
接下來(lái)我們要做關(guān)鍵的一步,實(shí)現(xiàn)InvocationHandler接口
最后,我們創(chuàng)建測(cè)試客戶端,
運(yùn)行的結(jié)果如下:
使用動(dòng)態(tài)代理的好處是你不必修改原有代碼FooImpl,但是一個(gè)缺點(diǎn)是你不得不寫(xiě)一個(gè)接口,如果你的類原來(lái)沒(méi)有實(shí)現(xiàn)接口的話。 4.3擴(kuò)展在上面的例子中演示了利用動(dòng)態(tài)代理比較兩個(gè)方法的執(zhí)行時(shí)間,有時(shí)候通過(guò)一次簡(jiǎn)單的測(cè)試進(jìn)行比較是片面的,因此可以進(jìn)行多次執(zhí)行測(cè)試對(duì)象,從而計(jì)算出最差、最好和平均性能。這樣,我們才能“加快經(jīng)常執(zhí)行的程序的速度,盡量少調(diào)用速度慢的程序”。 5 內(nèi)存消耗測(cè)試5.1 目標(biāo)當(dāng)一個(gè)java應(yīng)用程序運(yùn)行時(shí),有很多需要消耗內(nèi)存的因素存在,像對(duì)象、加載類、線程等。在這里只考慮程序中的對(duì)象所消耗的虛擬機(jī)堆空間,這樣我們就可以利用Runtime 類的freeMemory()和totalMemory()方法。 5.2 實(shí)現(xiàn)為了方便期間,我們首先添加一個(gè)類計(jì)算當(dāng)前內(nèi)存消耗。
然后修改Handler類的invoke()方法。
同時(shí)我們的測(cè)試用例也做了一下改動(dòng),測(cè)試同樣一個(gè)顯而易見(jiàn)的問(wèn)題,比較一個(gè)長(zhǎng)度為1000的ArrayList和HashMap所占空間的大小,接口、實(shí)現(xiàn)如下:
測(cè)試客戶端代碼如下:
測(cè)試結(jié)果如下:
結(jié)果一幕了然,可以看到,我們只需要修改invoke()方法,然后簡(jiǎn)單執(zhí)行客戶端調(diào)用就可以了。 6 結(jié)束語(yǔ)AOP通過(guò)分解關(guān)注點(diǎn)和OOP相得益彰,使程序更加簡(jiǎn)潔易懂,通過(guò)Java語(yǔ)言本身提供的動(dòng)態(tài)代理幫助我們很容易分解關(guān)注點(diǎn),取得了較好的效果。不過(guò)測(cè)試對(duì)象必須實(shí)現(xiàn)接口在一定程度上限制了動(dòng)態(tài)代理的使用,可以借鑒Spring中使用的CGlib來(lái)為沒(méi)有實(shí)現(xiàn)任何接口的類創(chuàng)建動(dòng)態(tài)代理。 7 參考資料本文中提到的一些性能測(cè)試概念主要來(lái)自http://java./docs/books/performance/ 一些AOP的概念來(lái)自Jboss的http://www./index.html?module=html&op=userdisplay&id=developers/projects/jboss/aop 動(dòng)態(tài)代理和AOP的某些知識(shí)來(lái)自http://www./docs/reference/aop.html |
|
來(lái)自: 鬼迷心竅 > 《Java相關(guān)》