反射获取注解:
上下文获取注解的代码:
getBeansWithAnnotation源码跟踪,找到真正调用,做了反射缓存:
结论:jdk 的java.lang.Class 反射获取,效率还好;
反射获取方法,invoke执行方法:
Java 方法反射性能差主要原因是:
1.获取Method对象慢
1.1 需要检查方法权限
每次获取Method对象,都需要检查方法权限是否合法,哪怕是已经调用的Method对象。
1.2 需要遍历筛选递归
每次都需要遍历筛选才能定位获取到Method对象,有时候甚至还需要递归向父类和接口遍历筛选获取Method对象
1.3 每一个Method都有一个root,不暴漏给外部,而是每次copy一个Method
每次都要拷贝一个Method对象
2.调用invoke方法慢
2.1 Method#invoke 方法会对参数做封装和解封操作
invoke 方法的参数是 Object[] 类型,也就是说,如果方法参数是简单类型的话,需要在此转化成 Object 类型,例如 long ,在 javac compile 的时候 用了Long.valueOf() 转型,也就大量了生成了Long 的 Object, 同时 传入的参数是Object[]数值,那还需要额外封装object数组。而在上面 MethodAccessorGenerator#emitInvoke 方法里我们看到,生成的字节码时,会把参数数组拆解开来,把参数恢复到没有被 Object[] 包装前的样子,同时还要对参数做校验,这里就涉及到了解封操作。因此,在反射调用的时候,因为封装和解封,产生了额外的不必要的内存浪费,当调用次数达到一定量的时候,还会导致 GC。
2.2 需要检查方法可见性
每次调用都必须检查方法的可见性(在 Method.invoke 里)
2.3 需要校验参数
每次调用都要检查每个实际参数与形式参数的类型匹配性(在NativeMethodAccessorImpl.invoke0 里或者生成的 Java 版 MethodAccessor.invoke 里)
2.4 invoke调用逻辑是委托给MethodAccessor的,而accessor对象会在第一次invoke的时候才创建,是一种lazy init方式
2.5 反射方法难以内联
内联概念:把函数调用的方法直接内嵌到方法内部,减少函数调用的次数。
native版的反射调用则无法被有效内联,因而调用开销无法随程序的运行而降低。
2.6 JIT 无法优化
因为反射涉及到动态加载的类型,所以无法进行优化。
反射获取方法的优化方案:
方案一,本地反射:
import jdk.internal.reflect.MethodAccessor;
import jdk.internal.reflect.MethodAccessor;
import jdk.internal.reflect.ReflectionFactory;
import sun.misc.Unsafe;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class UnsafeExample {
static final Unsafe unsafe;
static {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
} catch (Exception ex) {
throw new Error(ex);
}
}
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
String className = "com.dblones.java.reflect.example01.Animal";
Class clazz = Class.forName(className);
Constructor constructor = clazz.getConstructor();
Method method = clazz.getMethod("run", double.class);
method.setAccessible(true);
long noInflationOffset = unsafe.staticFieldOffset(ReflectionFactory.class.getDeclaredField("noInflation"));
Object reflectionFactoryClass = unsafe.staticFieldBase(ReflectionFactory.class.getDeclaredField("noInflation"));
unsafe.putBoolean(reflectionFactoryClass, noInflationOffset, true);
long methodAccessorOffset = unsafe.objectFieldOffset(Method.class.getDeclaredField("methodAccessor"));
ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();
MethodAccessor methodAccessor = reflectionFactory.newMethodAccessor(method);
unsafe.putObject(method, methodAccessorOffset, methodAccessor);
Object obj = constructor.newInstance();
long startTime = System.nanoTime();
for(int i = 0; i < 10000; i++) {
Object distance = (Object) method.invoke(obj, 2d*i);
}
long endTime = System.nanoTime();
System.out.println("调用耗时:" + (endTime - startTime));
}
}
import jdk.internal.reflect.MethodAccessor;
import jdk.internal.reflect.ReflectionFactory;
import sun.misc.Unsafe;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class UnsafeExample {
static final Unsafe unsafe;
static {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
} catch (Exception ex) {
throw new Error(ex);
}
}
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
String className = "com.dblones.java.reflect.example01.Animal";
Class clazz = Class.forName(className);
Constructor constructor = clazz.getConstructor();
Method method = clazz.getMethod("run", double.class);
method.setAccessible(true);
long noInflationOffset = unsafe.staticFieldOffset(ReflectionFactory.class.getDeclaredField("noInflation"));
Object reflectionFactoryClass = unsafe.staticFieldBase(ReflectionFactory.class.getDeclaredField("noInflation"));
unsafe.putBoolean(reflectionFactoryClass, noInflationOffset, true);
ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();
MethodAccessor methodAccessor = reflectionFactory.newMethodAccessor(method);
Object obj = constructor.newInstance();
long startTime = System.nanoTime();
for(int i = 0; i < 10000; i++) {
Object distance = (Object) methodAccessor.invoke(obj, new Object[]{2d*i});
}
long endTime = System.nanoTime();
System.out.println("调用耗时:" + (endTime - startTime));
}
}
方案二,三方反射:
import com.esotericsoftware.reflectasm.MethodAccess;
import com.esotericsoftware.reflectasm.MethodAccess;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class AsmReflectExample {
public static void main(String[] args) throws Throwable {
String className = "com.dblones.java.reflect.example01.Animal";
Class clazz = Class.forName(className);
Constructor constructor = clazz.getConstructor();
MethodAccess access = MethodAccess.get(clazz);
Integer index = access.getIndex("run", double.class);
Object obj = constructor.newInstance();
long startTime = System.nanoTime();
for(int i = 0; i < 10000; i++) {
Object distance = access.invoke(obj, index, 2d*i);
}
long endTime = System.nanoTime();
System.out.println("调用耗时:" + (endTime - startTime));
}
}
既然这样,咱们来看看JDK 1.8.0_331的反射代码,是不是实现了方案一的优化,sun公司已经优化了:
最后,结论是,可以直接放心的写反射代码了:
性能是有损失,差多少的问题。