Java6反射瓶颈与优化

查找的瓶颈

  Class.getMethods()方法会自动缓存(其他getMethod也一样),所以第一次查找会比较慢,再次查找就直接从缓存里取了。如果禁用了缓存每次都要去查询了,System.getProperty("sun.reflect.noCaches")默认为true。但每次调用getMethod方法还是都要做checkMemberAccess的验证,这个验证还没看,不知道需要花多少效率。

invoke的瓶颈

  用native code就不说了, 当调用次数达到一定的阀值的时候,sun jdk 会使用

MethodAccessorGenerator.generate 生成一个虚拟的class, GeneratedMethodAccessor1,2...之类

你可以在启动参数里 使用  XX:+TraceClassLoading, 可以看到 load 的虚拟的class

  从理论上来说,这样的调用等于是生成新的class,直接调用具体的类的方法,性能应该和直接调用的方法接近,或者类似,除了方法调用的入栈和出栈,当然当用hotspot编译运行的时候的,内联可以解决这样的问题。

  结果却让人吃惊

在用类反射调用的时候100万次需要3秒,而直接调用却只要10毫秒。

有人说,因为是method的调用,涉及到method的quickCheckMemberAccess,ensureMemberAccess检查,这个你可以通过设置method.setAccessor(true) 绕过成员检查。

主要的原因是: 因为接口的通用性,java 的invoke 方法 是传object, 和object[] 数组的。

也就是如果是简单类型的话,在接口处必须封装成object, 例如 long ,在javac compile的时候 用了Long.valueOf() 转型。

那也就是大量了生成了Long 的object, 同时 传入的参数是Object[]数值,那还需要额外封装object数组。

在调用的时候,产生了额外的不必要的内存浪费,当调用次数达到一定量的时候,最终还导致了GC。

接口的通用性在对性能要求不高的系统里通用性非常高,但在对性能要求高的系统里简直就是灾难。

 

优化方案

  为了避免大量简单类型的转型,大量的数组产生,提出解决方案

首先sun的method.invoke 是不可用了,因为invoke 的使用本来产生了大量的数组为了参数。

  一般我们类反射通常这样写:

Class="testclass";

Method="testmethod";

args[0].type="int";

value="1" ;

arg[1].type="long";

value=1; 

arg[2].type="byte";

value="3"; 

return="void";

 

构建arg 的基础类

public class arg{
   public int int1;
   public int int2;
   ...
   public long long1;
   public long long2;
   ...
   public byte byte1;
   ......
}

构建method 的基础类,第三个参数是代表返回的类型

public abstract class method{
    public abstract Object invoke(Object obj, arg args, Object o);
    public abstract  int invoke(Object obj,arg args, int i);
    public abstract void invoke(Object obj, arg args)
    public abstract args map(Object parameter);
   
}

然后分析刚才的arg[]的类型,大小,使用 ClassFileAssembler 生成一个虚拟的method1的类继承method,直接生成字节码,并且load到JVM里
而生成的class源码应该类似

public class method1 extends method{
      public void invoke(Object obj, arg args){
             test test = (test)obj;
             test.testmethod(args.int1,args.long1,arg.type1);
      }
     ThreadLocal local = new ThreadLocal();
     local.put(args);
     public void map(Object parameter){
         arg  args = (arg)local.get();
         args.Long1= 1;
         ....
     }
}

对args的参数赋值是在虚拟类里面直接赋值的,同时为了避免object args每次大量生成,可以吧object args 放入threadlocal, 绑定到线程,每次取出直接赋值就可以了。

posted @ 2014-01-14 17:20  寂静沙滩  阅读(499)  评论(0)    收藏  举报