Java的反射机制,相信很多人都已经用过,我们在使用的时候,最大的一个问题除了使用复杂之外,是性能隐忧,本文主要针对性能隐忧进行阐述。

反射为什么慢?

在stackoverflow上,已经有人问了这个问题:http://stackoverflow.com/questions/1392351/java-reflection-why-is-it-so-slow

有一个回复很多人支持,如下

  1. The compiler can do no optimization whatsoever as it can have no real idea about what you are doing. This probably goes for the JIT as well
  2. Everything being invoked/created has to be discovered (i.e. classes looked up by name, methods looked at for matches etc)
  3. Arguments need to be dressed up via boxing/unboxing, packing into arrays, Exceptions wrapped in InvocationTargetExceptions and re-thrown etc.
  4. All the processing that Jon Skeet mentions here.

简单意译:编译器不能对代码对优化,所有的反射操作都需要类似查表的操作,参数需要封装,解封装,异常也重新封装,rethrow等等。而四点具体的内容如下:

Compare that with everything that reflection has to do:

  • Check that there's a parameterless constructor
  • Check the accessibility of the parameterless constructor
  • Check that the caller has access to use reflection at all
  • Work out (at execution time) how much space needs to be allocated
  • Call into the constructor code (because it won't know beforehand that the constructor is empty)

基本上都是check这check那,还有无法被优化掉的调用。

所以,这些额外的操作,就可以理解为什么反射比较慢了,但是,究竟哪些环节比较耗时呢? 

反射究竟是什么地方慢?

我写了一段测试代码,可能有漏洞,欢迎大家指出! 


  
  1. class A{ 
  2.     public String field = ""
  3.     public boolean doSomething(){ 
  4.         return true
  5.     } 
  6. A object = new A();  
  7. Class c = A.class
  8.  
  9. int loops = 10000000
  10. A[] As = new A[loops]; 
  11. boolean boolRet = false
  12.  
  13. long start = System.currentTimeMillis();  
  14.    forint i = 0; i < loops; i++ )  
  15.    {  
  16.        As[i] = new A(); 
  17.    }  
  18.    System.out.println( loops + " regular instaniations:" + (System.currentTimeMillis() - start) + " milliseconds." ); 
  19.     
  20.    start = System.currentTimeMillis(); 
  21.    forint i = 0; i < loops; i++ )  
  22.    {  
  23.        As[i] = (A) c.newInstance(); 
  24.    }  
  25.    System.out.println( loops + " reflection instaniations:" + (System.currentTimeMillis() - start) + " milliseconds." ); 
  26.     
  27. start = System.currentTimeMillis();  
  28. forint i = 0; i < loops; i++ )  
  29. {  
  30.     boolRet = object.doSomething();  
  31. }  
  32. System.out.println( loops + " regular method calls:" + (System.currentTimeMillis() - start) + " milliseconds." ); 
  33.  
  34. java.lang.reflect.Method method = c.getMethod( "doSomething"null );  
  35. start = System.currentTimeMillis();  
  36. forint i = 0; i < loops; i++ )  
  37. {  
  38.     boolRet = (Boolean) method.invoke( object, null );  
  39. }  
  40. System.out.println( loops + " reflective method calls without lookup:" + (System.currentTimeMillis() - start) + " milliseconds." ); 
  41.  
  42. start = System.currentTimeMillis();  
  43. forint i = 0; i < loops; i++ )  
  44. {  
  45.     method = c.getMethod( "doSomething"null );  
  46.     method.invoke( object, null );  
  47. }  
  48. System.out.println( loops + " reflective method calls with lookup:" + (System.currentTimeMillis() - start) + " milliseconds." ); 
  49.  
  50. String fieldValue = ""
  51. start = System.currentTimeMillis(); 
  52.    forint i = 0; i < loops; i++ )  
  53.    {  
  54.        fieldValue = object.field; 
  55.    } 
  56.    System.out.println( loops + " regular field get value:" + (System.currentTimeMillis() - start) + " milliseconds." ); 
  57.     
  58.    Field field = null
  59.    field = c.getField("field"); 
  60.    start = System.currentTimeMillis(); 
  61.    forint i = 0; i < loops; i++ )  
  62.    {  
  63.        fieldValue = (String) field.get(object); 
  64.    }  
  65.    System.out.println( loops + " reflective field get value without lookup:" + (System.currentTimeMillis() - start) + " milliseconds." ); 
  66.     
  67. start = System.currentTimeMillis(); 
  68.    forint i = 0; i < loops; i++ )  
  69.    {  
  70.        field = c.getField("field"); 
  71.        fieldValue = (String) field.get(object); 
  72.    }  
  73.    System.out.println( loops + " reflective field get value with lookup:" + (System.currentTimeMillis() - start) + " milliseconds." ); 

执行后结果如下(JDK1.6):

10000000 regular instaniations:1557 milliseconds.
10000000 reflection instaniations:2893 milliseconds.

10000000 regular method calls:6 milliseconds.
10000000 reflective method calls without lookup:3460 milliseconds.
10000000 reflective method calls with lookup:10119 milliseconds.

10000000 regular field get value:5 milliseconds.
10000000 reflective field get value without lookup:3897 milliseconds.
10000000 reflective field get value with lookup:12560 milliseconds.

可见:

  1. 反射比较耗时,除了初始化对象之外,其他操作性能相差不是一个数量级
  2. 使用反射调用方法和获取成员值,查找比真正执行的操作更耗时 

 

反射的优化手段有什么?

基于以上测试的结果,我们可以有以下优化手段

  1. 不要重复反射
  2. 将最慢的反射结果缓存起来,如方法和成员的获取,从而较少“查找”地使用反射
  3. 使用代码动态生成技术,通过调用代理类的方式来模拟反射。如ReflectASM开源项目