技术开发 频道

Java程序性能测试

【IT168技术文档】概述  
   在开发中,性能测试是设计初期容易忽略的问题,开发人员会为了解决一个问题而“不择手段”,作者所参与的项目中也遇到了类似问题,字符串拼接、大量的网络调用和数据库访问等等都对系统的性能产生了影响,可是大家不会关心这些问题,“CPU速度在变快”,“内存在变大”,并且,“好像也没有那么慢吧”。  
       有很多商业的性能测试软件可供使用,如Jprofiler、JProbe  Profiler等,但在开发当中显得有些遥远而又昂贵。  
 
 2  目标  
     本文将讲述如何利用Java语言本身提供的方法在开发中进行性能测试,找到系统瓶颈,进而改进设计;并且在尽量不修改测试对象的情况下进行测试。                                             

3  预备知识  
         面向对象编程通过抽象继承采用模块化的思想来求解问题域,但是模块化不能很好的解决所有问题。有时,这些问题可能在多个模块中都出现,像日志功能,为了记录每个方法进入和离开时的信息,你不得不在每个方法里添加log("in  some  method")等信息。如何解决这类问题呢?将这些解决问题的功能点散落在多个模块中会使冗余增大,并且当很多个功能点出现在一个模块中时,代码变的很难维护。因此,AOP(Aspect  Oriented  Programming)应运而生。如果说OOP(Aobject  Oriented  Programming)关注的是一个类的垂直结构,那么AOP是从水平角度来看待问题。  

         动态代理类可以在运行时实现若干接口,每一个动态代理类都有一个Invocation  handler对象与之对应,这个对象实现了InvocationHandler接口,通过动态代理的接口对动态代理对象的方法调用会转而调用Invocation  handler对象的invoke方法,通过动态代理实例、方法对象和参数对象可以执行调用并返回结果。  

说到AOP,大家首先会想到的是日志记录、权限检查和事务管理,是的,AOP是解决这些问题的好办法。本文根据AOP的思想,通过动态代理来解决一类新的问题——性能测试(performance  testing)。  

性能测试主要包括以下几个方面:  

l  计算性能:可能是人们首先关心的,简单的说就是执行一段代码所用的时间  

l  内存消耗:程序运行所占用的内存大小  

l  启动时间:从你启动程序到程序正常运行的时间  

l  可伸缩性(scalability)  

l  用户察觉性能(perceived  performance):不是程序实际运行有多快,而是用户感觉程序运行有多快.  

本文主要给出了计算性能测试和内存消耗测试的可行办法。  

4  计算性能测试  
4.1  目标:  
通过该测试可以得到一个方法执行需要的时间  

4.2实现:  
         Java为我们提供了System.  currentTimeMillis()方法,可以得到毫秒级的当前时间,我们在以前的程序当中一定也写过类似的代码来计算执行某一段代码所消耗的时间。  
long start=System.currentTimeMillis(); doSth(); long end=System.currentTimeMillis(); System.out.println("time lasts "+(end-start)+"ms");
但是,在每个方法里面都写上这么一段代码是一件很枯燥的事情,我们通过Java的java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler利用动态代理来很好的解决上面的问题。  

我们要测试的例子是java.util.LinkedList和java.util.ArrayList的get(int  index)方法,显然ArrayList要比LinkedList高效,因为前者是随机访问,而后者需要顺序访问。  

首先我们创建一个接口  
public interface Foo { public void testArrayList(); public void testLinkedList(); }
然后我们创建测试对象实现这个接口  
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); } }
接下来我们要做关键的一步,实现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; } }
最后,我们创建测试客户端,
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(); } } }

 

运行的结果如下:

begin method testArrayList( ) the method testArrayList lasts 0ms end method testArrayList begin method testLinkedList( ) the method testLinkedList lasts 219ms end method testLinkedList
使用动态代理的好处是你不必修改原有代码FooImpl,但是一个缺点是你不得不写一个接口,如果你的类原来没有实现接口的话。  

4.3扩展  
     在上面的例子中演示了利用动态代理比较两个方法的执行时间,有时候通过一次简单的测试进行比较是片面的,因此可以进行多次执行测试对象,从而计算出最差、最好和平均性能。这样,我们才能“加快经常执行的程序的速度,尽量少调用速度慢的程序”。  
0
相关文章