目录
  1. 1. 谈一谈Java反射 (上)
    1. 1.1. 从 getMethod 和 getDeclaredMethod 说起
    2. 1.2. 继续深入 - getMethod0 方法
    3. 1.3. 再进一步 - getMethodsRecursive 方法
    4. 1.4. 走到底了 - privateGetDeclaredMethods 方法
    5. 1.5. 回过头来提一提 - searchMethod 方法
    6. 1.6. 再来看一看- getReflectionFactory()#copyMethod() 方法
    7. 1.7. Method#invoke 开始了, 从方法调用开始
      1. 1.7.1. Step 1. 检查调用权限
      2. 1.7.2. Step 2.获取 MethodAccessor 对象
      3. 1.7.3. 调用 MethodAccessor#invoke 对方法进行调用
    8. 1.8. 慢在哪里?
      1. 1.8.1. Drawbacks of Reflection
    9. 1.9. 结语
谈一谈Java反射 (上)

谈一谈Java反射 (上)

​ 小到Minecraft的注解框架, 大到Spring实例化其Beans, 我们难免都会接触到Java一个非常重要的功能 - 反射 ( Reflection ) , 但提到反射, 伴随而来的往往还有三个字 - 效 率 差 应该在实际开发中避免使用。 那么反射究竟在哪里慢 ?

​ 让我们从最基本的反射开始, 了解反射的调用过程, 再来看Java 7中新加入的 MethodHandle 如何提高反射的运行效率。

注意: 本文采用 Oracle JDK 13.01

getMethodgetDeclaredMethod 说起

​ 在使用反射的过程中, 我们往往会用到 getMethodgetDeclaredMethod 这两个方法, 用来获取类中方法的 Method 对象

​ 在 JDK 13 中, 这两个方法的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@CallerSensitive
public Method getMethod(String name, Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
Objects.requireNonNull(name);
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkMemberAccess(sm, Member.PUBLIC, Reflection.getCallerClass(), true);
}
Method method = getMethod0(name, parameterTypes);
if (method == null) {
throw new NoSuchMethodException(methodToString(name, parameterTypes));
}
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) {
checkMemberAccess(sm, Member.DECLARED, Reflection.getCallerClass(), true);
}
Method method = searchMethods(privateGetDeclaredMethods(false), name, parameterTypes);
if (method == null) {
throw new NoSuchMethodException(methodToString(name, parameterTypes));
}
return getReflectionFactory().copyMethod(method);
}

​ 通过源代码, 我们可以看出, 两种方法的逻辑基本一致, 都是检查方法权限, 通过 getMethod0 或者是 privateGetDeclaredMethods 获取 Method 对象, 然后再返回 Method 对象的拷贝

​ 在检查方法权限的部分, 我们可以看出, 在 getMethod 方法下, 传入的参数为 Member.PUBLIC , 而在 getDeclaredMethod 中传入的则是 Member.DECLARED , 查阅源代码我们可以知道, 两个量均为定义在 Member 接口中的整型常量, 其中 PUBLIC 包括该类(或接口)所有的访问权限为 public 的方法也包括继承的成员, 而 DECLARED 则是该类所有声明的成员, 包括 public , protected , private 的成员, 但不包括继承的成员。具体代码及文档如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;

...

}

继续深入 - getMethod0 方法

​ 以下是 getMethod0 的代码, 通过注释我们可以知道, getMethod0 的返回值是一个 rootMethod 对象, 并且强调这个 root 对象不应该暴露到外部, 而应该通过 ReflectionFactory.copyMethod 拷贝

1
2
3
4
5
6
7
8
9
10
// Returns a "root" Method object. This Method object must NOT
// be propagated to the outside world, but must instead be copied
// via ReflectionFactory.copyMethod.
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 对象, 并通过其 getMostSpecific 方法, 筛选出最为明确具体的方法返回

注: 或者说当一个方法签名子类和父类同时满足要求时, 优先选择子类而不是父类

再进一步 - getMethodsRecursive 方法

​ 以下为 getMethodsRecursive 的源代码, 可以看出注释同样是强调该方法返回的 root Method 对象不应该暴露于外部, 而应该通过 ReflectionFactory.copyMethod 拷贝

​ 不过有趣的一点是, 在这里我们看到了在 getDeclaredMethod 方法中出现的 privateGetDeclaredMethods 方法, 不过不同的是, 这里的 publicOnly 参数传入的是 true 而非 false

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// Returns a list of "root" Method objects. These Method objects must NOT
// be propagated to the outside world, but must instead be copied
// via ReflectionFactory.copyMethod.
private PublicMethods.MethodList getMethodsRecursive(String name,
Class<?>[] parameterTypes,
boolean includeStatic) {
// 1st check declared public methods
Method[] methods = privateGetDeclaredMethods(/* publicOnly */ true);
PublicMethods.MethodList res = PublicMethods.MethodList
.filter(methods, name, parameterTypes, includeStatic);
// if there is at least one match among declared methods, we need not
// search any further as such match surely overrides matching methods
// declared in superclass(es) or interface(s).
if (res != null) {
return res;
}

// if there was no match among declared methods,
// we must consult the superclass (if any) recursively...
Class<?> sc = getSuperclass();
if (sc != null) {
res = sc.getMethodsRecursive(name, parameterTypes, includeStatic);
}

// ...and coalesce the superclass methods with methods obtained
// from directly implemented interfaces excluding static methods...
for (Class<?> intf : getInterfaces(/* cloneArray */ false)) {
res = PublicMethods.MethodList.merge(
res, intf.getMethodsRecursive(name, parameterTypes,
/* includeStatic */ false));
}

return res;
}

​ 通过阅读源代码我们可以发现, 这里获取方法分为三个步骤:

  1. 通过privateGetDeclaredMethods获取自己所有的 public方法, 并通过MethodList#filter
    方法过滤方法, 找出所有满足条件的方法 , 如果至少有一个满足条件的方法, 便不再继续搜索而是直接返回, 因为它肯定 override 了父类或者接口的对应方法
  2. 如果没有在自己的方法中找到, 便去递归 搜索父类的的方法
  3. 如果没有在自己的方法中找到, 便去递归搜索自己所有接口的方法

​ 观察上述三个步骤我们可以发现, 最终获取方法, 我们还是要通过 privateGetDeclaredMethods 方法, 所以接下来, 让我们去深入了解一下它的实现

走到底了 - privateGetDeclaredMethods 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Returns an array of "root" methods. These Method objects must NOT
// be propagated to the outside world, but must instead be copied
// via ReflectionFactory.copyMethod.
private Method[] privateGetDeclaredMethods(boolean publicOnly) {
Method[] res;
ReflectionData<T> rd = reflectionData();
if (rd != null) {
res = publicOnly ? rd.declaredPublicMethods : rd.declaredMethods;
if (res != null) return res;
}
// No cached value available; request value from VM
res = Reflection.filterMethods(this, getDeclaredMethods0(publicOnly));
if (rd != null) {
if (publicOnly) {
rd.declaredPublicMethods = res;
} else {
rd.declaredMethods = res;
}
}
return res;
}

​ 终于, 我们来到了 getMethodgetDeclaredMethod 方法的终点, 观察他的方法, 似乎也很简单, 如果存在缓存则直接使用缓存获取 Method 对象的数组, 如果不存在缓存,就从 JVM 获取并将其存入缓存

​ 那么, 缓存对象的 ReflectionData到底是个什么样的?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* Reflection support.
*/

// Reflection data caches various derived names and reflective members. Cached
// values may be invalidated when JVM TI RedefineClasses() is called
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;

ReflectionData(int redefinedCount) {
this.redefinedCount = redefinedCount;
}
}

​ 可以看到, ReflectionData 类中缓存了Class中的所有属性和方法以及构造函数, 甚至是类的 simpleNamecanonicalName

​ 让我们再来看看 reflectionData 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Lazily create and cache ReflectionData
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 的软引用作为缓存, 如果缓存为空, 或者软引用已经被GC , 或者缓存已经过期, 就使用 newReflectionData 方法重建缓存, 并替换掉旧的缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private ReflectionData<T> newReflectionData(SoftReference<ReflectionData<T>> oldReflectionData,
int classRedefinedCount) {
while (true) {
ReflectionData<T> rd = new ReflectionData<>(classRedefinedCount);
// try to CAS it...
if (Atomic.casReflectionData(this, oldReflectionData, new SoftReference<>(rd))) {
return rd;
}
// else retry
oldReflectionData = this.reflectionData;
classRedefinedCount = this.classRedefinedCount;
if (oldReflectionData != null &&
(rd = oldReflectionData.get()) != null &&
rd.redefinedCount == classRedefinedCount) {
return rd;
}
}
}

​ 至此, getMethod 部分的代码已经结束

回过头来提一提 - searchMethod 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// This method does not copy the returned Method object!
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;
}

​ 可以看出, 这个方法其实相当简单明了, 判断方法的签名是与给定的相同

再来看一看- getReflectionFactory()#copyMethod() 方法

​ 方法的注释中反复提到, 得到的 root Method 对象不能暴露给外界, 而需要通过 ** getReflectionFactory()#copyMethod() ** 方法拷贝

1
2
3
4
5
6
/** Makes a copy of the passed method. The returned method is a
"child" of the passed one; see the comments in Method.java for
details. */
public Method copyMethod(Method arg) {
return langReflectAccess().copyMethod(arg);
}

​ 根据注释,实际上最后调用的是Method#copy方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
**
* Package-private routine (exposed to java.lang.Class via
* ReflectAccess) which returns a copy of this Method. The copy's
* "root" field points to this 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;
}

​ 可以看出, 该方法只能拷贝 root Method 对象, 并且在实例化了拷贝后的 Method 对象后, 将其 root 属性设置为当前的 root Method 对象

​ 同时, 根据注释以及源代码, 所有的指向同一个底层方法的 Method 对象都会共用一个 MethodAcessor 对象,

​ 到这里看起来, 似乎在Java反射的获取 Method 对象环节并没有什么特别明显的效率开销的问题, 那么, 接下来我们再来看看, 方法是怎样被 invoke

Method#invoke 开始了, 从方法调用开始

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
/**
* Invokes the underlying method represented by this {@code Method}
* object, on the specified object with the specified parameters.
* Individual parameters are automatically unwrapped to match
* primitive formal parameters, and both primitive and reference
* parameters are subject to method invocation conversions as
* necessary.
*
* <p>If the underlying method is static, then the specified {@code obj}
* argument is ignored. It may be null.
*
* <p>If the number of formal parameters required by the underlying method is
* 0, the supplied {@code args} array may be of length 0 or null.
*
* <p>If the underlying method is an instance method, it is invoked
* using dynamic method lookup as documented in The Java Language
* Specification, section 15.12.4.4; in particular,
* overriding based on the runtime type of the target object may occur.
*
* <p>If the underlying method is static, the class that declared
* the method is initialized if it has not already been initialized.
*
* <p>If the method completes normally, the value it returns is
* returned to the caller of invoke; if the value has a primitive
* type, it is first appropriately wrapped in an object. However,
* if the value has the type of an array of a primitive type, the
* elements of the array are <i>not</i> wrapped in objects; in
* other words, an array of primitive type is returned. If the
* underlying method return type is void, the invocation returns
* null.
*
* @param obj the object the underlying method is invoked from
* @param args the arguments used for the method call
* @return the result of dispatching the method represented by
* this object on {@code obj} with parameters
* {@code args}
*
* @exception IllegalAccessException if this {@code Method} object
* is enforcing Java language access control and the underlying
* method is inaccessible.
* @exception IllegalArgumentException if the method is an
* instance method and the specified object argument
* is not an instance of the class or interface
* declaring the underlying method (or of a subclass
* or implementor thereof); if the number of actual
* and formal parameters differ; if an unwrapping
* conversion for primitive arguments fails; or if,
* after possible unwrapping, a parameter value
* cannot be converted to the corresponding formal
* parameter type by a method invocation conversion.
* @exception InvocationTargetException if the underlying method
* throws an exception.
* @exception NullPointerException if the specified object is null
* and the method is an instance method.
* @exception ExceptionInInitializerError if the initialization
* provoked by this method fails.
*/
@CallerSensitive
@ForceInline // to ensure Reflection.getCallerClass optimization
@HotSpotIntrinsicCandidate
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
if (!override) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz,
Modifier.isStatic(modifiers) ? null : obj.getClass(),
modifiers);
}
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
ma = acquireMethodAccessor();
}
return ma.invoke(obj, args);
}

​ 可以看到, invoke 方法的实现分为上个步骤

  1. 检查是否有调用权限

  2. 获取 MethodAccessor 对象

  3. 调用 MethodAccessor#invoke 对方法进行调用

    Step 1. 检查调用权限

    ​ 如果override 属性为true 则跳过检查, 即调用Method#setAccessible(true) 为设置 override 属性为 true

    Step 2.获取 MethodAccessor 对象

    ​ 获取方法的 MethodAccessor 如果为空, 则调用 acquireMethodAccessor 方法获取 MethodAccessor

    acquireMethodAccessor 方法的源代码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // NOTE that there is no synchronization used here. It is correct
    // (though not efficient) to generate more than one MethodAccessor
    // for a given Method. However, avoiding synchronization will
    // probably make the implementation more scalable.
    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;
    }

    ​ 可以看到, 这里的方法并没有 同步化 ( synchronization ) , 注释也解释说, 为给定的方法,生成多于一个的 MethodAccessor 对象是没有问题的, 尽管它可能会不够高效, 但避免同步化, 也使得这个实现更加灵活

    ​ 通过源代码我们可以看到, 他会尝试先获取 root Method 对象的 MethodAccessor 对象, 如果其为空, 则通过 ReflectionFactory#newMethodAccessor 方法新建一个 MethodAccessor 对象, 并将其设置为当前 MethodMethodAccessor

    ​ 下面是 ReflectionFactory#newMethodAccessor 的源代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    public MethodAccessor newMethodAccessor(Method method) {
    checkInitted();

    if (Reflection.isCallerSensitive(method)) {
    Method altMethod = findMethodForReflection(method);
    if (altMethod != null) {
    method = altMethod;
    }
    }

    // use the root Method that will not cache caller class
    Method root = langReflectAccess().getRoot(method);
    if (root != null) {
    method = root;
    }

    if (noInflation && !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) {
    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;
    }
    }

    ​ 我们可以看到, 该方法会先调用 ReflectionFactory#checkInitted 方法, 检查 java.lang.reflect.Methodstatic initializer 是否已经完成初始化

    ​ 以下是 ReflectionFactory#checkInitted 方法的源代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    /** We have to defer full initialization of this class until after
    the static initializer is run since java.lang.reflect.Method's
    static initializer (more properly, that for
    java.lang.reflect.AccessibleObject) causes this class's to be
    run, before the system properties are set up. */
    private static void checkInitted() {
    if (initted) return;

    // Defer initialization until module system is initialized so as
    // to avoid inflation and spinning bytecode in unnamed modules
    // during early startup.
    if (!VM.isModuleSystemInited()) {
    return;
    }

    Properties props = GetPropertyAction.privilegedGetProperties();
    String val = props.getProperty("sun.reflect.noInflation");
    if (val != null && val.equals("true")) {
    noInflation = true;
    }

    val = props.getProperty("sun.reflect.inflationThreshold");
    if (val != null) {
    try {
    inflationThreshold = Integer.parseInt(val);
    } catch (NumberFormatException e) {
    throw new RuntimeException("Unable to parse property sun.reflect.inflationThreshold", e);
    }
    }

    disableSerialConstructorChecks =
    "true".equals(props.getProperty("jdk.disableSerialConstructorChecks"));

    initted = true;
    }

    ​ 可以看到, 其中设置了在 ** ReflectionFactory#newMethodAccessor ** 方法中使用到的 noInflation 属性

    ​ 对于 sun.reflect.noInflation 属性, Oracle的Blog上是这么解释的

    sun.reflect.noInflation

    This boolean will disable inflation (the default use of JNI before the threshold is reached). In other words, if this is set to true, we immediately skip to generating a pure-Java implementation on the first access. (default: false)

    ​ 从代码中我们可以看到, 当 noInflation 属性直接设置为 true 会直接采用纯Java版本的MethodAcessorImplMagicAccessorImpl , 去生成 Java Bytecode

    ​ 默认情况下我们会采用 NativeMethodAcessorImpl 以及采用 DelegatingMethodAccessorImpl 做代理的方式实现

    ​ 以下是 NativeMethodAcessorImpl 以及 DelegatingMethodAccessorImpl 的源代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    /** Used only for the first few invocations of a Method; afterward,
    switches to bytecode-based implementation */

    class NativeMethodAccessorImpl extends MethodAccessorImpl {
    private final Method method;
    private DelegatingMethodAccessorImpl parent;
    private int numInvocations;

    NativeMethodAccessorImpl(Method method) {
    this.method = method;
    }

    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())) {
    MethodAccessorImpl acc = (MethodAccessorImpl)
    new MethodAccessorGenerator().
    generateMethod(method.getDeclaringClass(),
    method.getName(),
    method.getParameterTypes(),
    method.getReturnType(),
    method.getExceptionTypes(),
    method.getModifiers());
    parent.setDelegate(acc);
    }

    return invoke0(method, obj, args);
    }

    void setParent(DelegatingMethodAccessorImpl parent) {
    this.parent = parent;
    }

    private static native Object invoke0(Method m, Object obj, Object[] args);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    /** Delegates its invocation to another MethodAccessorImpl and can
    change its delegate at run time. */

    class DelegatingMethodAccessorImpl extends MethodAccessorImpl {
    private MethodAccessorImpl delegate;

    DelegatingMethodAccessorImpl(MethodAccessorImpl delegate) {
    setDelegate(delegate);
    }

    public Object invoke(Object obj, Object[] args)
    throws IllegalArgumentException, InvocationTargetException
    {
    return delegate.invoke(obj, args);
    }

    void setDelegate(MethodAccessorImpl delegate) {
    this.delegate = delegate;
    }
    }

    ​ 通过 NativeMethodAccessorImpl 的源代码和注释我们可以知道, ** NativeMethodAccessorImpl** 仅仅只被用于低于 inflationThreshold 次的调用, 其内部维护了一个 numInvocations 的变量作为计数器, 当超过阈值时, 会采用 **MagicAccessorImpl 生成 Java Bytecode

    ​ 该阈值为 ** sun.reflect.inflationThreshold** 属性

    sun.reflect.inflationThreshold

    This integer specifies the number of times a method will be accessed via the JNI implementation before a custom pure-Java accessor is generated. (default: 15)

    ​ 这也是为什么会使用 DelegatingMethodAccessorImpl 作为代理

    ​ 根据注释

    **“Inflation” mechanism. **

    ​ Loading bytecodes to implement
    ​ Method.invoke() and Constructor.newInstance() currently costs
    ​ 3-4x more than an invocation via native code for the first
    ​ invocation (though subsequent invocations have been benchmarked
    ​ to be over 20x faster). Unfortunately this cost increases
    ​ startup time for certain applications that use reflection
    ​ intensively (but only once per class) to bootstrap themselves.
    ​ To avoid this penalty we reuse the existing JVM entry points
    ​ for the first few invocations of Methods and Constructors and
    ​ then switch to the bytecode-based implementations.

    ​ Java版本的MagicAcessorImpl 的调用效率是 NativeMethodAcessorImpl 的 20 倍以上, 但初次调用生成的是否会比 Native 版本的慢 3 - 4倍, 这带来了更长的启动时间

    ​ 这也是为什么 Java 反射会采用这种策略

调用 MethodAccessor#invoke 对方法进行调用

​ 在我们得到了 MethodAccessor 对象后, 就可以通过其 invoke 方法实现最终的调用操作

NativeMethodAccessorImplinvoke 方法为一个计数器加调用一个名为 invoke0native 方法

​ 即

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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())) {
MethodAccessorImpl acc = (MethodAccessorImpl)
new MethodAccessorGenerator().
generateMethod(method.getDeclaringClass(),
method.getName(),
method.getParameterTypes(),
method.getReturnType(),
method.getExceptionTypes(),
method.getModifiers());
parent.setDelegate(acc);
}

return invoke0(method, obj, args);
}

DelegatingMethodAccessorImplinvoke 方法更不用说, 直接调用代理对象的 invoke 方法

​ 即

1
2
3
4
5
public Object invoke(Object obj, Object[] args)
throws IllegalArgumentException, InvocationTargetException
{
return delegate.invoke(obj, args);
}

​ 而 MagicAcessorImpl 通过 MethodAccessorGenerator#generate 方法生成字节码之后, 会调用MethodAcessorGenerator#emitInvoke 方法, 为invokenewInstance 方法生成实际的调用

​ 其源代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
/** This emits the code for either invoke() or newInstance() */
private void emitInvoke() {
// NOTE that this code will only handle 65535 parameters since we
// use the sipush instruction to get the array index on the
// operand stack.
if (parameterTypes.length > 65535) {
throw new InternalError("Can't handle more than 65535 parameters");
}

// Generate code into fresh code buffer
ClassFileAssembler cb = new ClassFileAssembler();
if (isConstructor) {
// 1 incoming argument
cb.setMaxLocals(2);
} else {
// 2 incoming arguments
cb.setMaxLocals(3);
}

short illegalArgStartPC = 0;

if (isConstructor) {
// Instantiate target class before continuing
// new <target class type>
// dup
cb.opc_new(targetClass);
cb.opc_dup();
} else {
// Get target object on operand stack if necessary.

// We need to do an explicit null check here; we won't see
// NullPointerExceptions from the invoke bytecode, since it's
// covered by an exception handler.
if (!isStatic()) {
// aload_1
// ifnonnull <checkcast label>
// new <NullPointerException>
// dup
// invokespecial <NullPointerException ctor>
// athrow
// <checkcast label:>
// aload_1
// checkcast <target class's type>
cb.opc_aload_1();
Label l = new Label();
cb.opc_ifnonnull(l);
cb.opc_new(nullPointerClass);
cb.opc_dup();
cb.opc_invokespecial(nullPointerCtorIdx, 0, 0);
cb.opc_athrow();
l.bind();
illegalArgStartPC = cb.getLength();
cb.opc_aload_1();
cb.opc_checkcast(targetClass);
}
}

// Have to check length of incoming array and throw
// IllegalArgumentException if not correct. A concession to the
// JCK (isn't clearly specified in the spec): we allow null in the
// case where the argument list is zero length.
// if no-arg:
// aload_2 | aload_1 (Method | Constructor)
// ifnull <success label>
// aload_2 | aload_1
// arraylength
// sipush <num parameter types>
// if_icmpeq <success label>
// new <IllegalArgumentException>
// dup
// invokespecial <IllegalArgumentException ctor>
// athrow
// <success label:>
Label successLabel = new Label();
if (parameterTypes.length == 0) {
if (isConstructor) {
cb.opc_aload_1();
} else {
cb.opc_aload_2();
}
cb.opc_ifnull(successLabel);
}
if (isConstructor) {
cb.opc_aload_1();
} else {
cb.opc_aload_2();
}
cb.opc_arraylength();
cb.opc_sipush((short) parameterTypes.length);
cb.opc_if_icmpeq(successLabel);
cb.opc_new(illegalArgumentClass);
cb.opc_dup();
cb.opc_invokespecial(illegalArgumentCtorIdx, 0, 0);
cb.opc_athrow();
successLabel.bind();

// 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).

short paramTypeCPIdx = nonPrimitiveParametersBaseIdx;
Label nextParamLabel = null;
byte count = 1; // both invokeinterface opcode's "count" as well as
// num args of other invoke bytecodes
for (int i = 0; i < parameterTypes.length; i++) {
Class<?> paramType = parameterTypes[i];
count += (byte) typeSizeInStackSlots(paramType);
if (nextParamLabel != null) {
nextParamLabel.bind();
nextParamLabel = null;
}
// aload_2 | aload_1
// sipush <index>
// aaload
if (isConstructor) {
cb.opc_aload_1();
} else {
cb.opc_aload_2();
}
cb.opc_sipush((short) i);
cb.opc_aaload();
if (isPrimitive(paramType)) {
// Unboxing code.
// Put parameter into temporary local variable
// astore_3 | astore_2
if (isConstructor) {
cb.opc_astore_2();
} else {
cb.opc_astore_3();
}

// 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

Label l = null; // unboxing label
nextParamLabel = new Label();

for (int j = 0; j < primitiveTypes.length; j++) {
Class<?> c = primitiveTypes[j];
if (canWidenTo(c, paramType)) {
if (l != null) {
l.bind();
}
// Emit checking and unboxing code for this type
if (isConstructor) {
cb.opc_aload_2();
} else {
cb.opc_aload_3();
}
cb.opc_instanceof(indexForPrimitiveType(c));
l = new Label();
cb.opc_ifeq(l);
if (isConstructor) {
cb.opc_aload_2();
} else {
cb.opc_aload_3();
}
cb.opc_checkcast(indexForPrimitiveType(c));
cb.opc_invokevirtual(unboxingMethodForPrimitiveType(c),
0,
typeSizeInStackSlots(c));
emitWideningBytecodeForPrimitiveConversion(cb,
c,
paramType);
cb.opc_goto(nextParamLabel);
}
}

if (l == null) {
throw new InternalError
("Must have found at least identity conversion");
}

// Fell through; given object is null or invalid. According to
// the spec, we can throw IllegalArgumentException for both of
// these cases.

l.bind();
cb.opc_new(illegalArgumentClass);
cb.opc_dup();
cb.opc_invokespecial(illegalArgumentCtorIdx, 0, 0);
cb.opc_athrow();
} else {
// Emit appropriate checkcast
cb.opc_checkcast(paramTypeCPIdx);
paramTypeCPIdx = add(paramTypeCPIdx, S2);
// Fall through to next argument
}
}
// Bind last goto if present
if (nextParamLabel != null) {
nextParamLabel.bind();
}

short invokeStartPC = cb.getLength();

// OK, ready to perform the invocation.
if (isConstructor) {
cb.opc_invokespecial(targetMethodRef, count, 0);
} else {
if (isStatic()) {
cb.opc_invokestatic(targetMethodRef,
count,
typeSizeInStackSlots(returnType));
} else {
if (isInterface()) {
cb.opc_invokeinterface(targetMethodRef,
count,
count,
typeSizeInStackSlots(returnType));
} else {
cb.opc_invokevirtual(targetMethodRef,
count,
typeSizeInStackSlots(returnType));
}
}
}

short invokeEndPC = cb.getLength();

if (!isConstructor) {
// Box return value if necessary
if (isPrimitive(returnType)) {
cb.opc_invokestatic(boxingMethodForPrimitiveType(returnType),
typeSizeInStackSlots(returnType),
0);
} else if (returnType == Void.TYPE) {
cb.opc_aconst_null();
}
}
cb.opc_areturn();

// We generate two exception handlers; one which is responsible
// for catching ClassCastException and NullPointerException and
// throwing IllegalArgumentException, and the other which catches
// all java/lang/Throwable objects thrown from the target method
// and wraps them in InvocationTargetExceptions.

short classCastHandler = cb.getLength();

// ClassCast, etc. exception handler
cb.setStack(1);
cb.opc_invokespecial(toStringIdx, 0, 1);
cb.opc_new(illegalArgumentClass);
cb.opc_dup_x1();
cb.opc_swap();
cb.opc_invokespecial(illegalArgumentStringCtorIdx, 1, 0);
cb.opc_athrow();

short invocationTargetHandler = cb.getLength();

// InvocationTargetException exception handler
cb.setStack(1);
cb.opc_new(invocationTargetClass);
cb.opc_dup_x1();
cb.opc_swap();
cb.opc_invokespecial(invocationTargetCtorIdx, 1, 0);
cb.opc_athrow();

// Generate exception table. We cover the entire code sequence
// with an exception handler which catches ClassCastException and
// converts it into an IllegalArgumentException.

ClassFileAssembler exc = new ClassFileAssembler();

exc.emitShort(illegalArgStartPC); // start PC
exc.emitShort(invokeStartPC); // end PC
exc.emitShort(classCastHandler); // handler PC
exc.emitShort(classCastClass); // catch type

exc.emitShort(illegalArgStartPC); // start PC
exc.emitShort(invokeStartPC); // end PC
exc.emitShort(classCastHandler); // handler PC
exc.emitShort(nullPointerClass); // catch type

exc.emitShort(invokeStartPC); // start PC
exc.emitShort(invokeEndPC); // end PC
exc.emitShort(invocationTargetHandler); // handler PC
exc.emitShort(throwableClass); // catch type

emitMethod(invokeIdx, cb.getMaxLocals(), cb, exc,
new short[] { invocationTargetClass });
}
通过这段代码的注释, 我们可以发现, 生成的 **invoke** 方法对传入的参数进行了校验工作, 并尝试了所有可能的扩展操作

​ 至此, Java反射的流程已经介绍完毕

慢在哪里?

  1. 反射需要查找类的方法表, 需要对方法进行遍历操作

  2. 反射调用时, Method#invoke 方法和 emitInvoke 方法会对传入的参数进行校验, 其中会进行 boxingunboxing 操作

  3. 每次反射调用都需要校验方法的可见性

  4. 反射调用方法难以被JIT优化, 难以被内联操作

    即Oracle的文档中提到的

    Drawbacks of Reflection

    Reflection is powerful, but should not be used indiscriminately. If it is possible to perform an operation without using reflection, then it is preferable to avoid using it. The following concerns should be kept in mind when accessing code via reflection.

    • Performance Overhead

      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.

    • Security Restrictions

      Reflection requires a runtime permission which may not be present when running under a security manager. This is in an important consideration for code which has to run in a restricted security context, such as in an Applet.

    • Exposure of Internals

      Since reflection allows code to perform operations that would be illegal in non-reflective code, such as accessing private fields and methods, the use of reflection can result in unexpected side-effects, which may render code dysfunctional and may destroy portability. Reflective code breaks abstractions and therefore may change behavior with upgrades of the platform.

结语

​ 本篇文章, 从 getMethod 方法说起到 invoke 过了一遍 Java 的方法调用反射, 简单分析了 传统的反射为什么效率低下

​ 而下一篇文章, 将介绍于 JDK7 引入的 MethodHandle , 并介绍其如何优化反射调用

文章作者: Twiliness
文章链接: https://twiliness.xyz/2019/10/26/Java-Reflection-01/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Twilight Spring