隨筆 - 55  文章 - 187  trackbacks - 0
          <2008年1月>
          303112345
          6789101112
          13141516171819
          20212223242526
          272829303112
          3456789

          常用鏈接

          留言簿(12)

          隨筆分類

          隨筆檔案

          groovy

          搜索

          •  

          最新評論

          閱讀排行榜

          評論排行榜

          Java程序性能測試

          1 概述

          在開發(fā)中,性能測試是設(shè)計初期容易忽略的問題,開發(fā)人員會為了解決一個問題而“不擇手段”,作者所參與的項目中也遇到了類似問題,字符串拼接、大量的網(wǎng)絡(luò)調(diào)用和數(shù)據(jù)庫訪問等等都對系統(tǒng)的性能產(chǎn)生了影響,可是大家不會關(guān)心這些問題,“CPU速度在變快”,“內(nèi)存在變大”,并且,“好像也沒有那么慢吧”。

          有很多商業(yè)的性能測試軟件可供使用,如JprofilerJProbe Profiler等,但在開發(fā)當(dāng)中顯得有些遙遠(yuǎn)而又昂貴。

          2 目標(biāo)

          本文將講述如何利用Java語言本身提供的方法在開發(fā)中進(jìn)行性能測試,找到系統(tǒng)瓶頸,進(jìn)而改進(jìn)設(shè)計;并且在盡量不修改測試對象的情況下進(jìn)行測試。

          3 預(yù)備知識

          面向?qū)ο缶幊掏ㄟ^抽象繼承采用模塊化的思想來求解問題域,但是模塊化不能很好的解決所有問題。有時,這些問題可能在多個模塊中都出現(xiàn),像日志功能,為了記錄每個方法進(jìn)入和離開時的信息,你不得不在每個方法里添加log("in some method")等信息。如何解決這類問題呢?將這些解決問題的功能點散落在多個模塊中會使冗余增大,并且當(dāng)很多個功能點出現(xiàn)在一個模塊中時,代碼變的很難維護(hù)。因此,AOPAspect Oriented Programming)應(yīng)運而生。如果說OOPAobject Oriented Programming)關(guān)注的是一個類的垂直結(jié)構(gòu),那么AOP是從水平角度來看待問題。

          動態(tài)代理類可以在運行時實現(xiàn)若干接口,每一個動態(tài)代理類都有一個Invocation handler對象與之對應(yīng),這個對象實現(xiàn)了InvocationHandler接口,通過動態(tài)代理的接口對動態(tài)代理對象的方法調(diào)用會轉(zhuǎn)而會調(diào)用Invocation handler對象的invoke方法,通過動態(tài)代理實例、方法對象和參數(shù)對象可以執(zhí)行調(diào)用并返回結(jié)果。

          說到AOP,大家首先會想到的是日志記錄、權(quán)限檢查和事務(wù)管理,是的,AOP是解決這些問題的好辦法。本文根據(jù)AOP的思想,通過動態(tài)代理來解決一類新的問題——性能測試(performance testing)。

          性能測試主要包括以下幾個方面:

          l         計算性能:可能是人們首先關(guān)心的,簡單的說就是執(zhí)行一段代碼所用的時間

          l         內(nèi)存消耗:程序運行所占用的內(nèi)存大小

          l         啟動時間:從你啟動程序到程序正常運行的時間

          l         可伸縮性(scalability)

          l         用戶察覺性能(perceived performance):不是程序?qū)嶋H運行有多快,而是用戶感覺程序運行有多快.

          本文主要給出了計算性能測試和內(nèi)存消耗測試的可行辦法。

           

          4 計算性能測試

          4.1 目標(biāo):

          通過該測試可以得到一個方法執(zhí)行需要的時間

          4.2實現(xiàn):

          Java為我們提供了System. currentTimeMillis()方法,可以得到毫秒級的當(dāng)前時間,我們在以前的程序當(dāng)中一定也寫過類似的代碼來計算執(zhí)行某一段代碼所消耗的時間。

                     long start=System.currentTimeMillis();

                     doSth();

                     long end=System.currentTimeMillis();

                     System.out.println("time lasts "+(end-start)+"ms");

          但是,在每個方法里面都寫上這么一段代碼是一件很枯燥的事情,我們通過Javajava.lang.reflect.Proxyjava.lang.reflect.InvocationHandler利用動態(tài)代理來很好的解決上面的問題。

          我們要測試的例子是java.util.LinkedListjava.util.ArrayListget(int index)方法顯然ArrayList要比LinkedList高效,因為前者是隨機訪問,而后者需要順序訪問。

          首先我們創(chuàng)建一個接口

          public interface Foo {

              public void testArrayList();

              public void testLinkedList();

          }

          然后我們創(chuàng)建測試對象實現(xiàn)這個接口

          public class FooImpl implements Foo {

           

                 private List link=new LinkedList();

                 private List array=new ArrayList();

           

                 public FooImpl()

                 {

                     for(int i=0;i<10000;i++)

                     {

                            array.add(new Integer(i));

                            link.add(new Integer(i));

                     }           

                 }

             

              public void testArrayList()

              {

                     for(int i=0;i<10000;i++)

                            array.get(i);

              }

             

              public void testLinkedList()

              {

                     for(int i=0;i<10000;i++)

                            link.get(i);

              }

          }

          接下來我們要做關(guān)鍵的一步,實現(xiàn)InvocationHandler接口

          import java.lang.reflect.InvocationHandler;

          import java.lang.reflect.Method;

          import java.lang.reflect.*;

           

          public class Handler implements InvocationHandler {

           

                 private Object obj;

           

              public Handler(Object obj) {

                  this.obj = obj;

              }

           

              public static Object newInstance(Object obj) {

                  Object result = Proxy.newProxyInstance(obj.getClass().getClassLoader(),

                          obj.getClass().getInterfaces(), new Handler(obj));

           

                  return (result);

              }

           

              public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                  Object result;

                  try {

                      System.out.print("begin method " + method.getName() + "(");

                      for (int i = 0; args != null && i < args.length; i++) {

           

                          if (i > 0) System.out.print(",");

                          System.out.print(" " +

                                  args[i].toString());

                      }

                      System.out.println(" )");

                      long start=System.currentTimeMillis();

                      result = method.invoke(obj, args);

                      long end=System.currentTimeMillis();

                      System.out.println("the method "+method.getName()+" lasts "+(end-start)+"ms");

                  } catch (InvocationTargetException e) {

                      throw e.getTargetException();

                  } catch (Exception e) {

                      throw new RuntimeException

                              ("unexpected invocation exception: " +

                              e.getMessage());

                  } finally {

                      System.out.println("end method " + method.getName());

                  }

                  return result;

              }

          }

          最后,我們創(chuàng)建測試客戶端,

          public class TestProxy {

              public static void main(String[] args) {

                  try {

                      Foo foo = (Foo) Handler.newInstance(new FooImpl());

                      foo.testArrayList();

                      foo.testLinkedList();

                  } catch (Exception e) {

                      e.printStackTrace();

                  }

          }

          }

          運行的結(jié)果如下:

          begin method testArrayList( )

          the method testArrayList lasts 0ms

          end method testArrayList

          begin method testLinkedList( )

          the method testLinkedList lasts 219ms

          end method testLinkedList

          使用動態(tài)代理的好處是你不必修改原有代碼FooImpl,但是一個缺點是你不得不寫一個接口,如果你的類原來沒有實現(xiàn)接口的話。

          4.3擴展

          在上面的例子中演示了利用動態(tài)代理比較兩個方法的執(zhí)行時間,有時候通過一次簡單的測試進(jìn)行比較是片面的,因此可以進(jìn)行多次執(zhí)行測試對象,從而計算出最差、最好和平均性能。這樣,我們才能“加快經(jīng)常執(zhí)行的程序的速度,盡量少調(diào)用速度慢的程序”。

           

          5 內(nèi)存消耗測試

          5.1 目標(biāo)

          當(dāng)一個java應(yīng)用程序運行時,有很多需要消耗內(nèi)存的因素存在,像對象、加載類、線程等。在這里只考慮程序中的對象所消耗的虛擬機堆空間,這樣我們就可以利用Runtime 類的freeMemory()totalMemory()方法。

          5.2 實現(xiàn)

          為了方便期間,我們首先添加一個類計算當(dāng)前內(nèi)存消耗。

          class Memory

          {

                 public static long used()

                 {

                        long total=Runtime.getRuntime().totalMemory();

                        long free=Runtime.getRuntime().freeMemory();

                        return (total-free);

                 }

          }

          然后修改Handler類的invoke()方法。

          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                  Object result;

                  try {

                      System.out.print("begin method " + method.getName() + "(");

                      for (int i = 0; args != null && i < args.length; i++) {

           

                          if (i > 0) System.out.print(",");

                          System.out.print(" " +

                                  args[i].toString());

                      }

                      System.out.println(" )");

                      long start=Memory.used();

                      result = method.invoke(obj, args);

                      long end=Memory.used();

                      System.out.println("memory increased by "+(end-start)+"bytes");

                  } catch (InvocationTargetException e) {

                      throw e.getTargetException();

                  } catch (Exception e) {

                      throw new RuntimeException

                              ("unexpected invocation exception: " +

                              e.getMessage());

                  } finally {

                      System.out.println("end method " + method.getName());

                  }

                  return result;

              }

          同時我們的測試用例也做了一下改動,測試同樣一個顯而易見的問題,比較一個長度為1000ArrayListHashMap所占空間的大小,接口、實現(xiàn)如下:

          public interface MemoConsumer {

                 public void creatArray();

                 public void creatHashMap();

          }

          public class MemoConsumerImpl implements MemoConsumer {

           

                 ArrayList arr=null;

                 HashMap hash=null;

           

                 public void creatArray() {

           

                        arr=new ArrayList(1000);

                 }

                 public void creatHashMap() {

                        hash=new HashMap(1000);

                 }

          }

          測試客戶端代碼如下:

                         MemoConsumer arrayMemo=(MemoConsumer)Handler.newInstance(new MemoConsumerImpl ());

                         arrayMemo.creatArray();

                         arrayMemo.creatHashMap();

          測試結(jié)果如下:

          begin method creatArray( )

          memory increased by 4400bytes

          end method creatArray

          begin method creatHashMap( )

          memory increased by 4480bytes

          end method creatHashMap

          結(jié)果一幕了然,可以看到,我們只需要修改invoke()方法,然后簡單執(zhí)行客戶端調(diào)用就可以了。

          6 結(jié)束語

                 AOP通過分解關(guān)注點和OOP相得益彰,使程序更加簡潔易懂,通過Java語言本身提供的動態(tài)代理幫助我們很容易分解關(guān)注點,取得了較好的效果。不過測試對象必須實現(xiàn)接口在一定程度上限制了動態(tài)代理的使用,可以借鑒Spring中使用的CGlib來為沒有實現(xiàn)任何接口的類創(chuàng)建動態(tài)代理。

          7 參考資料

          本文中提到的一些性能測試概念主要來自http://java.sun.com/docs/books/performance/

          一些AOP的概念來自Jbosshttp://www.jboss.org/index.html?module=html&op=userdisplay&id=developers/projects/jboss/aop

          動態(tài)代理和AOP的某些知識來自http://www.springframework.org/docs/reference/aop.html

           

          8 作者聲明

          東西寫的一般,不過是我辛勤勞動所為,轉(zhuǎn)載請注明出處,可以通過whq3721@163.com 與我聯(lián)系,http://freshman.52blog.net



          本文來自:http://blog.csdn.net/freshmanya/archive/2004/11/28/196893.aspx

          筆記:
              文章寫得很好,很實用。
              采用java本身的動態(tài)代理機制實現(xiàn)的AOP來測試實現(xiàn)了某個接口的類的各個方法的性能,很妙。
              正如作者所說的,這種方式的局限性就在于只能測試實現(xiàn)了某接口的類的測試。采用AspectJ應(yīng)該會是更好的方式。

          --------------------

              WE準(zhǔn)高手
          posted on 2008-01-24 13:13 大衛(wèi) 閱讀(1965) 評論(4)  編輯  收藏 所屬分類: Java

          FeedBack:
          # re: 找到一篇性能測試的好文,簡單實用,收藏之。 2008-01-24 18:53 王能
          那像這個NBA中文網(wǎng):http://www.yaonba.com 怎測試呢?  回復(fù)  更多評論
            
          # re: 找到一篇性能測試的好文,簡單實用,收藏之。 2008-01-25 10:31 大衛(wèi)
          @王能
          不知道你是否在開玩笑 :)
          我這里談的是針對方法的性能測試,你說的當(dāng)然需要另外的一套理論和技術(shù)。  回復(fù)  更多評論
            
          # re: 找到一篇性能測試的好文,簡單實用,收藏之。 2008-01-25 17:51 lx281
          1樓明顯是廣告……  回復(fù)  更多評論
            
          # re: 找到一篇性能測試的好文,簡單實用,收藏之。 2008-01-27 11:38 海邊沫沫
          就是,一樓在我的博客中也推薦過NBA中文網(wǎng)。
          不過這個網(wǎng)站的內(nèi)容還不錯。

          針對整個網(wǎng)站進(jìn)行測試,當(dāng)然是集成測試和壓力測試了。  回復(fù)  更多評論
            
          主站蜘蛛池模板: 渭南市| 固阳县| 河池市| 石泉县| 抚州市| 辽中县| 米林县| 房产| 云梦县| 蕲春县| 五家渠市| 宣化县| 通州市| 宁德市| 江西省| 贡嘎县| 哈巴河县| 石林| 东方市| 永丰县| 富裕县| 贡觉县| 无锡市| 华蓥市| 虹口区| 恩施市| 喀喇沁旗| 襄樊市| 波密县| 新源县| 阜阳市| 什邡市| 乌什县| 图片| 商南县| 米林县| 邵阳市| 汝阳县| 隆回县| 绥滨县| 五家渠市|