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, 绑定到线程,每次取出直接赋值就可以了。

浙公网安备 33010602011771号