概述
我们在Java开发中,难免会接触到反射,而在一些框架中,反射的运用更是常见
,每次提到反射,大家第一反应都是反射效率低,尽量少使用。但是反射的效率
低的原因在哪里?我们先来看看获取反射的方法
获取反射的方法
class MethodReflect {
public static void main(String[] args) {
try {
Class clazz = Class.forName("com.ble.kotlinscope.reflect.MethodReflect");
Object refTest = clazz.newInstance();
Method method = clazz.getDeclaredMethod("refMethod");
method.invoke(refTest);
} catch (Exception e) {
e.printStackTrace();
}
}
public void refMethod() {
System.out.println("refMethod");
}
}
我们在调用反射时,首先创建Class对象,然后获取其Method对象,调用invoke方法。获取反射方法时,有两个方法,getMethod和getDeclaredMethod,我们就从这两个方法开始,分析下反射的原理。采用的时JDK12。
getMethod/getDeclaredMethod
class Class {
@CallerSensitive
public Method getMethod(String name, Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
Objects.requireNonNull(name);
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
// 1. 检查方法权限
checkMemberAccess(sm, Member.PUBLIC, Reflection.getCallerClass(), true);
}
// 2. 获取方法
Method method = getMethod0(name, parameterTypes);
if (method == null) {
throw new NoSuchMethodException(methodToString(name, parameterTypes));
}
// 3. 返回方法的拷贝
return getReflectionFactory().copyMethod(method);
}
@CallerSensitive
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
Objects.requireNonNull(name);
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
// 1. 检查方法是权限
checkMemberAccess(sm, Member.DECLARED, Reflection.getCallerClass(), true);
}
// 2. 获取方法
Method method = searchMethods(privateGetDeclaredMethods(false), name, parameterTypes);
if (method == null) {
throw new NoSuchMethodException(methodToString(name, parameterTypes));
}
// 3. 返回方法的拷贝
return getReflectionFactory().copyMethod(method);
}
}
从上面的代码,我们可以看到,获取方法的流程分三步走:
- 1.检测方法权限
- 2.获取方法Method对象
- 返回方法的拷贝
这里主要有两个区别:
1.getMethod中checkMemebersAccess传入的是Member.PUBLIC,而getDeclaredMethod传入的是Member.DECLARED,这两个值有什么区别呢?
- 返回方法的拷贝
public
interface Member {
/**
* Identifies the set of all public members of a class or interface,
* including inherited members.
*/
public static final int PUBLIC = 0;
/**
* Identifies the set of declared members of a class or interface.
* Inherited members are not included.
*/
public static final int DECLARED = 1;
}
注释里清楚的解释了PUBLIC和DECLARED的不同,PUBLIC会包括所有的public方法,包括父类的方法,而DECLARED会包括所有自己定义的方法,public,protected,private都在此,但是不包括父类的方法。这也正式getMethod和getDeclaredMethod的区别。
上面我们说过获取方法分三步走:
- 检测方法权限
- 获取方法Method对象
- 返回方法的拷贝
checkMemberAccess
class Class {
private void checkMemberAccess(SecurityManager sm, int which,
Class<?> caller, boolean checkProxyInterfaces) {
/* Default policy allows access to all {@link Member#PUBLIC} members,
* as well as access to classes that have the same class loader as the caller.
* In all other cases, it requires RuntimePermission("accessDeclaredMembers")
* permission.
*/
final ClassLoader ccl = ClassLoader.getClassLoader(caller);
if (which != Member.PUBLIC) {
final ClassLoader cl = getClassLoader0();
if (ccl != cl) {
sm.checkPermission(SecurityConstants.CHECK_MEMBER_ACCESS_PERMISSION);
}
}
this.checkPackageAccess(sm, ccl, checkProxyInterfaces);
}
}
在这里可以看到,对于非Member.PUBLIC的访问,会增加一项检测,SecurityManager.checkPermission(SecurityConstants.CHECK_MEMBER_ACCESS_PERMISSION);这项检测需要运行时申请。
getMethod0
class Class {
private Method getMethod0(String name, Class<?>[] parameterTypes) {
PublicMethods.MethodList res = getMethodsRecursive(
name,
parameterTypes == null ? EMPTY_CLASS_ARRAY : parameterTypes,
/* includeStatic */ true);
return res == null ? null : res.getMostSpecific();
}
}
这里时通过getMethodsRecursive获取到MethodList对象,然后通过
MethodList#getMostSpecific方法选出对应的方法
getMethodsRecursive
class Class {
private PublicMethods.MethodList getMethodsRecursive(String name,
Class<?>[] parameterTypes,
boolean includeStatic) {
// 1. 获取自己的 public 方法
Method[] methods = privateGetDeclaredMethods(/* publicOnly */ true);
// 2. 筛选符合条件的方法,构造 MethodList 对象
PublicMethods.MethodList res = PublicMethods.MethodList
.filter(methods, name, parameterTypes, includeStatic);
// 找到方法,直接返回
if (res != null) {
return res;
}
// 3. 没有找到方法,就获取其父类,递归调用 getMethodsRecursive 方法
Class<?> sc = getSuperclass();
if (sc != null) {
res = sc.getMethodsRecursive(name, parameterTypes, includeStatic);
}
// 4. 获取接口中对应的方法
for (Class<?> intf : getInterfaces(/* cloneArray */ false)) {
res = PublicMethods.MethodList.merge(
res, intf.getMethodsRecursive(name, parameterTypes,
/* includeStatic */ false));
}
return res;
}
}
这里获取方法有四步:
- 通过privateGetDeclaredMethods获取自己所有的public方法
- 通过MethodList#filter查找方法名,参数相同的方法,如果找到,直接返回
- 如果自己没有实现对应的方法,就去父类中查找对应的方法
- 查找接口中对应的方法
通过上面四个步骤,最终获取到的是一个MethodList对象,是一个链表结点,其next指向下一个结点。也就是说,这里获取到Method会有多个。
这里稍微解释下,我们平时编写Java代码时,通一个类时不能有方法名和方法参数都相同的方法,而实际上,在JVM中,一个方法签名是和返回值,方法名,方法参数三者相关的。也就是说,在JVM中,可以存在方法名和方法参数相同,但是返回值不同的方法.
所以这里返回的是一个方法链表。
所以上面最终返回方法时会通过MethodList#getMostSpecific进行返回值的匹配,匹配出返回值类型最具体的方法。
- 查找接口中对应的方法
privateGetDeclaredMethods
class Class {
private Method[] privateGetDeclaredMethods(boolean publicOnly) {
Method[] res;
// 1. 通过缓存获取 Method[]
ReflectionData<T> rd = reflectionData();
if (rd != null) {
res = publicOnly ? rd.declaredPublicMethods : rd.declaredMethods;
if (res != null) return res;
}
// 2. 没有缓存,通过 JVM 获取
res = Reflection.filterMethods(this, getDeclaredMethods0(publicOnly));
if (rd != null) {
if (publicOnly) {
rd.declaredPublicMethods = res;
} else {
rd.declaredMethods = res;
}
}
return res;
}
}
在priateGetDeclaredMethods获取方法时,有两个步骤:
- relectionData通过缓存获取
- 如果缓存没有命中的话,通过getDeclaredMethods0获取方法
先看看relectionData方法:
class Class {
private ReflectionData<T> reflectionData() {
SoftReference<ReflectionData<T>> reflectionData = this.reflectionData;
int classRedefinedCount = this.classRedefinedCount;
ReflectionData<T> rd;
if (reflectionData != null &&
(rd = reflectionData.get()) != null &&
rd.redefinedCount == classRedefinedCount) {
return rd;
}
// else no SoftReference or cleared SoftReference or stale ReflectionData
// -> create and replace new instance
return newReflectionData(reflectionData, classRedefinedCount);
}
}
在class中会维护一个ReflectionData的软引用,作为反射数据的缓存。ReflectionData结构如下:
private static class ReflectionData<T> {
volatile Field[] declaredFields;
volatile Field[] publicFields;
volatile Method[] declaredMethods;
volatile Method[] publicMethods;
volatile Constructor<T>[] declaredConstructors;
volatile Constructor<T>[] publicConstructors;
// Intermediate results for getFields and getMethods
volatile Field[] declaredPublicFields;
volatile Method[] declaredPublicMethods;
volatile Class<?>[] interfaces;
// Cached names
String simpleName;
String canonicalName;
static final String NULL_SENTINEL = new String();
// Value of classRedefinedCount when we created this ReflectionData instance
final int redefinedCount;
}
可以看到,保存了Class中的属性和方法,如果缓存为空,就会通过getDeclaredMethods0从JVM中查找方法。
getDeclaredMethods0时一个native方法,这里暂时先不看。
通过上面几个步骤,就获取到 Method 数组。
这就是getMethod方法的整体实现了。
我们再回过头看一下getDeclaredMethods方法的实现,通过privateGetDeclaredMethods获取方法后,会通过searchMethods对方法进行筛选。
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
// ...
Method method = searchMethods(privateGetDeclaredMethods(false), name, parameterTypes);
// ...
}
searchMethods方法实现比较简单,就是对比方法名,参数,方法返回值。
class Class {
private static Method searchMethods(Method[] methods,
String name,
Class<?>[] parameterTypes)
{
ReflectionFactory fact = getReflectionFactory();
Method res = null;
for (Method m : methods) {
// 比较方法名
if (m.getName().equals(name)
// 比较方法参数
&& arrayContentsEq(parameterTypes,
fact.getExecutableSharedParameterTypes(m))
// 比较返回值
&& (res == null
|| (res.getReturnType() != m.getReturnType()
&& res.getReturnType().isAssignableFrom(m.getReturnType()))))
res = m;
}
return res;
}
}
Method.copy
在获取到对应方法以后,并不会直接返回,而是通过getReflectionFactory().copyMethod(method);返回方法的一个拷贝。最终调用的是Method.copy(),我们来看看其实现。
class Method {
Method copy() {
// This routine enables sharing of MethodAccessor objects
// among Method objects which refer to the same underlying
// method in the VM. (All of this contortion is only necessary
// because of the "accessibility" bit in AccessibleObject,
// which implicitly requires that new java.lang.reflect
// objects be fabricated for each reflective call on Class
// objects.)
if (this.root != null)
throw new IllegalArgumentException("Can not copy a non-root Method");
Method res = new Method(clazz, name, parameterTypes, returnType,
exceptionTypes, modifiers, slot, signature,
annotations, parameterAnnotations, annotationDefault);
res.root = this;
// Might as well eagerly propagate this if already present
res.methodAccessor = methodAccessor;
return res;
}
}
会new一个Method实例并返回
这里有两点要注意:
1.设置root=this
2.会给Method设置MethodAccessor,用于后面方法调用。也就是所有的Method的拷贝都会使用同一份methodAccessor。
通过以上的步骤,就可以获取到了需要反射的方法。
调用反射方法
获取到方法以后,通过Method.invoke调用方法
class Method {
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
if (!override) {
Class<?> caller = Reflection.getCallerClass();
// 1. 检查权限
checkAccess(caller, clazz,
Modifier.isStatic(modifiers) ? null : obj.getClass(),
modifiers);
}
// 2. 获取 MethodAccessor
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
// 创建 MethodAccessor
ma = acquireMethodAccessor();
}
// 3. 调用 MethodAccessor.invoke
return ma.invoke(obj, args);
}
}
invoke方法的实现,分为三步:
检测是否有权限调用方法
这里对override变量进行判断,如果override==true,就跳过检查,我们通常在Method#invoke之前,会调用Method#setAccessible(true),就是设置override值为true。
获取MethodAccessor
在上面获取Method的时候我们讲到过,Method.copy会给Method的methodAccessor赋值。所以这里的methodAccessor就是拷贝时使用的MethodAccessor。如果ma为空,就去创建MethodAccessor。
class Method {
private MethodAccessor acquireMethodAccessor() {
// First check to see if one has been created yet, and take it
// if so
MethodAccessor tmp = null;
if (root != null) tmp = root.getMethodAccessor();
if (tmp != null) {
methodAccessor = tmp;
} else {
// Otherwise fabricate one and propagate it up to the root
tmp = reflectionFactory.newMethodAccessor(this);
setMethodAccessor(tmp);
}
return tmp;
}
}
这里会先查找root的MethodAccessor,这里的root在上面Method.copy中设置过。
如果还是没有找到,就去创建MethodAccessor
class ReflectionFactory {
public MethodAccessor newMethodAccessor(Method method) {
// 其中会对 noInflation 进行赋值
checkInitted();
// ...
if (noInflation && !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) {
// 生成的是 MethodAccessorImpl
return new MethodAccessorGenerator().
generateMethod(method.getDeclaringClass(),
method.getName(),
method.getParameterTypes(),
method.getReturnType(),
method.getExceptionTypes(),
method.getModifiers());
} else {
NativeMethodAccessorImpl acc =
new NativeMethodAccessorImpl(method);
DelegatingMethodAccessorImpl res =
new DelegatingMethodAccessorImpl(acc);
acc.setParent(res);
return res;
}
}
}
这里可以看到,一共有三种MethodAccessor。MethodAccessorImpl,NativeMethodAccessorImpl,DelegatingMethodAccessorImpl。
采用哪种 MethodAccessor 根据 noInflation 进行判断,noInflation 默认值为 false,只有指定了 sun.reflect.noInflation 属性为 true,才会 采用 MethodAccessorImpl。
所以默认会调用 NativeMethodAccessorImpl。
NativeMethodAccessorImpl 是 Native 版本的 MethodAccessor 实现。
class NativeMethodAccessorImpl extends MethodAccessorImpl {
public Object invoke(Object obj, Object[] args)
throws IllegalArgumentException, InvocationTargetException
{
// We can't inflate methods belonging to vm-anonymous classes because
// that kind of class can't be referred to by name, hence can't be
// found from the generated bytecode.
if (++numInvocations > ReflectionFactory.inflationThreshold()
&& !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) {
// Java 版本的 MethodAccessor
MethodAccessorImpl acc = (MethodAccessorImpl)
new MethodAccessorGenerator().
generateMethod(method.getDeclaringClass(),
method.getName(),
method.getParameterTypes(),
method.getReturnType(),
method.getExceptionTypes(),
method.getModifiers());
parent.setDelegate(acc);
}
// Native 版本调用
return invoke0(method, obj, args);
}
private static native Object invoke0(Method m, Object obj, Object[] args);
}
在 NativeMethodAccessorImpl 的实现中,我们可以看到,有一个 numInvocations 阀值控制,numInvocations 表示调用次数。如果 numInvocations 大于 15(默认阀值是 15),那么就使用 Java 版本的 MethodAccessorImpl。
为什么采用这个策略呢,可以 JDK 中的注释:
Java 版本的 MethodAccessorImpl 调用效率比 Native 版本要快 20 倍以上,但是 Java 版本加载时要比 Native 多消耗 3-4 倍资源,所以默认会调用 Native 版本,如果调用次数超过 15 次以后,就会选择运行效率更高的 Java 版本。
调用MethodAccessor.invoke实现方法调用
在生成 MethodAccessor 以后,就调用其 invoke 方法进行最终的反射调用。
这里我们对 Java 版本的 MethodAccessorImpl 做个简单的分析,Native 版本暂时不做分析。
在前面我们提到过 MethodAccessorImpl 是通过 MethodAccessorGenerator#generate 生成动态字节码然后动态加载到 JVM 中的。
其中生成 invoke 方法字节码的是 MethodAccessorGenerator#emitInvoke。
我们看其中校验参数的一小段代码:
// Iterate through incoming actual parameters, ensuring that each
// is compatible with the formal parameter type, and pushing the
// actual on the operand stack (unboxing and widening if necessary).
// num args of other invoke bytecodes
for (int i = 0; i < parameterTypes.length; i++) {
// ...
if (isPrimitive(paramType)) {
// Unboxing code.
// Put parameter into temporary local variable
// astore_3 | astore_2
// ...
// repeat for all possible widening conversions:
// aload_3 | aload_2
// instanceof <primitive boxing type>
// ifeq <next unboxing label>
// aload_3 | aload_2
// checkcast <primitive boxing type> // Note: this is "redundant",
// // but necessary for the verifier
// invokevirtual <unboxing method>
// <widening conversion bytecode, if necessary>
// goto <next parameter label>
// <next unboxing label:> ...
// last unboxing label:
// new <IllegalArgumentException>
// dup
// invokespecial <IllegalArgumentException ctor>
// athrow
}
}
通过上面的注释以及字节码,我们可以看到,生成的invoke方法,会对传入的参数做校验,其中涉及到unboxing操作。
到此,基本上Java方法反射的原理就介绍完了。
Java反射效率低的原因
了解了反射的原理以后,我们来分析一下反射效率低的原因。
- Method#invoke 方法会对参数做封装和解封操作
我们可以看到,invoke 方法的参数是 Object[] 类型,也就是说,如果方法参数是简单类型的话,需要在此转化成 Object 类型,例如 long ,在 javac compile 的时候 用了Long.valueOf() 转型,也就大量了生成了Long 的 Object, 同时 传入的参数是Object[] 数值,那还需要额外封装 object 数组。
- Method#invoke 方法会对参数做封装和解封操作
而在上面 MethodAccessorGenerator#emitInvoke 方法里我们看到,生成的字节码时,会把参数数组拆解开来,把参数恢复到没有被 Object[] 包装前的样子,同时还要对参数做校验,这里就涉及到了解封操作。
因此,在反射调用的时候,因为封装和解封,产生了额外的不必要的内存浪费,当调用次数达到一定量的时候,还会导致 GC。
- 需要检查方法可见性
通过上面的源码分析,我们会发现,反射时每次调用都必须检查方法的可见性(在 Method.invoke 里)
- 需要检查方法可见性
- 需要校验参数
反射时也必须检查每个实际参数与形式参数的类型匹配性(在NativeMethodAccessorImpl.invoke0 里或者生成的 Java 版 MethodAccessor.invoke 里)
- 需要校验参数
- 反射方法难以内联
Method#invoke 就像是个独木桥一样,各处的反射调用都要挤过去,在调用点上收集到的类型信息就会很乱,影响内联程序的判断,使得 Method.invoke() 自身难以被内联到调用方。
- 反射方法难以内联
- JIT 无法优化
在 JavaDoc 中提到:
Because reflection involves types that are dynamically resolved, certain Java virtual machine optimizations can not be performed. Consequently, reflective operations have slower performance than their non-reflective counterparts, and should be avoided in sections of code which are called frequently in performance-sensitive applications.
因为反射涉及到动态加载的类型,所以无法进行优化。
- JIT 无法优化
网友评论