在本系列的第8节,我们总结了一下jvm怎么执行方法调用,是在类中维护一个方法表,指向的是方法的实际内存地址。查找内存地址的执行时间不通,可以划分为静态绑定和动态绑定。那么反射是怎么执行的呢
public void invokeTest() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Class deClazs = Demo.class;
Method sayHel = deClazs.getMethod("sayHello", int.class);
sayHel.setAccessible(true);
sayHel.invoke(null,1);
}
static class Demo{
static void sayHello(int i){
System.out.println(i);
}
}
上面的代码是简单的反射调用,查看反射代码
@CallerSensitive
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
ma = acquireMethodAccessor();
}
return ma.invoke(obj, args);
}
性能消耗:
- Class.getMethod()
- invoke自动组装变长参数Object..args
- int 类型的拆箱装箱
- MethodAccessor的绑定
发现是调用的MethodAccessor 来执行的。这个地方靠的是委派机制,因为MethodAccessor有两个实现
- NativeMethodAccessorImpl:本地执行方法,是c语言实现,执行较慢,需要经过java-》C-》java的切换,
- DelegatingMethodAccessorImpl:动态代理实现,生成字节码,直接调用invoke方法,执行较快,但字节码生成速度较慢。只有在超过15此的阈值之后才会启动字节码生成和方法替换。
但是动态代理有个问题,因为实际实现方法可能比较多,在MethodAccessor中缓存的方法profile(目标类+方法名称)只有默认的两个,在多个实现的时候,动态代理的内联就不能派上用场,可以调整参数:-XX:TypeProfileWidth 增大缓存的记录