invoke 源码分析

摘要:最近有使用到Method的invoke方法,于是就学习了下Method的invoke方法源码(暂未深入到native)

正文:

源码分析

首先看一下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
class AccessibleObject implements AnnotatedElement {
boolean override;
//访问权限
public boolean isAccessible() {
return override;
}
}
//Method.class
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);
}

可以看到在该方法第一步会先去判断AccessibleObject的override属性是否为true

  • 若为true则忽略访问权限的控制
  • 若为false则会去调用Reflection.quickCheckMemberAccess()判断是不是public,若不是则会使用Reflection.getCallerClass()获取调用此方法的class,然后校验其是否有权限
  • 最后会调用MethodAccessor的invoke()方法

MethodAccessor的invoke方法源码如下所示,就是一个接口:

1
2
3
4
5
public interface MethodAccessor {
/** Matches specification in {@link java.lang.reflect.Method} */
public Object invoke(Object obj, Object[] args)
throws IllegalArgumentException, InvocationTargetException;
}

可以看到它只是一个单方法接口,其invoke()方法与Method.invoke()的对应。 创建MethodAccessor实例的是ReflectionFactory。

sun.reflect.ReflectionFactory

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
public class ReflectionFactory {

private static boolean initted = false;

// ...

//
// "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.
//
// Package-private to be accessible to NativeMethodAccessorImpl
// and NativeConstructorAccessorImpl
private static boolean noInflation = false;
private static int inflationThreshold = 15;

// ...

/** 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;
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
// Tests to ensure the system properties table is fully
// initialized. This is needed because reflection code is
// called very early in the initialization process (before
// command-line arguments have been parsed and therefore
// these user-settable properties installed.) We assume that
// if System.out is non-null then the System class has been
// fully initialized and that the bulk of the startup code
// has been run.

if (System.out == null) {
// java.lang.System not yet fully initialized
return null;
}

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

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

initted = true;
return null;
}
});
}

// ...

public MethodAccessor newMethodAccessor(Method method) {
checkInitted();

if (noInflation) {
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实现有两个版本,一个是Java实现的,另一个是native code实现的。Java实现的版本在初始化时需要较多时间,但长久来说性能较好;native版本正好相反,启动时相对较快,但运行时间长了之后速度就比不过Java版了。

这个需要注意的是inflationThreshold的值是15,也就是说前15次是使用的native版本,之后使用的是java版本,具体实现可以往下看。

为了权衡两个版本的性能,Sun的JDK使用了“inflation”的技巧:让Java方法在被反射调用时,开头若干次使用native版,等反射调用次数超过阈值时则生成一个专用的MethodAccessor实现类,生成其中的invoke()方法的字节码,以后对该Java方法的反射调用就会使用Java版。 (Sun的JDK是从1.4系开始采用这种优化的)

可以在启动命令里加上-Dsun.reflect.noInflation=true,就会RefactionFactorynoInflation属性就变成true了,这样不用等到15调用后,程序一开始就会用java版的MethodAccessor了

可以在上段代码newMethodAccessor()方法看到DelegatingMethodAccessorImpl res = new DelegatingMethodAccessorImpl(acc);

sun.reflect.DelegatingMethodAccessorImpl

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

这个类是方便在native与Java版的MethodAccessor之间实现切换。

sun.reflect.NativeMethodAccessorImpl

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
class NativeMethodAccessorImpl extends MethodAccessorImpl {
private Method method;
private DelegatingMethodAccessorImpl parent;
private int numInvocations;

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

public Object invoke(Object obj, Object[] args)
throws IllegalArgumentException, InvocationTargetException
{
if (++numInvocations > ReflectionFactory.inflationThreshold()) {
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);
}

可以看到在每次调用invoke方法时候会++numInvocations,inflationThreshold的值是15,该块就是上文所说的native版本和java版本的切换实现部分。当numInvocations超过inflationThreshold的值调用MethodAccessorGenerator.generateMethod()来生成Java版的MethodAccessor的实现类,并且改变DelegatingMethodAccessorImpl所引用的MethodAccessor为Java版。后续经由DelegatingMethodAccessorImpl.invoke()调用到的就是Java版的实现了。

反射的性能

反射的性能开销主要原因如下:

  • 可变参数导致的 Object 数组
  • 基本类型的自动装箱拆箱
  • MethodAccesor.invoke的瓶颈

前两点很好理解,最后一点说之前我们先说下方法内联:

方法内联指的是编译器在编译一个方法时,将某个方法调用的目标方法也纳入编译范围内,并用其返回值替代原方法调用这么个过程

在反射中,Method.invoke一直会被内联,但是它里面的MethodAccesor.invoke则不一定(java 虚拟机无法同时记录那么多调用者的具体类型),这样就会造成反射调用没有被内联的情况

测试用例

可以使用demo测试invoke方法执行的流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class A {
public void foo(String name) {
System.out.println("Hello," + name);
}
}

//Test
public class TestClassLoad {
public static void main(String[] args) throws Exception {
Class<?> clz = Class.forName("testinvoke.A");
Object o = clz.newInstance();
Method m = clz.getMethod("foo", String.class);
for (int i = 0; i < 16; i++) {
m.invoke(o, Integer.toString(i));
}
}
}
}

可以在运行的时候使用-XX:+TraceClassLoading参数监控类加载情况:

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
[Loaded testinvoke.A from file:/C:/Users/itliusir/git/test/Test/bin/]
[Loaded sun.reflect.NativeMethodAccessorImpl from D:\jdk8\jre\lib\rt.jar]
[Loaded sun.reflect.DelegatingMethodAccessorImpl from D:\jdk8\jre\lib\rt.jar]
Hello,0
Hello,1
Hello,2
Hello,3
Hello,4
Hello,5
Hello,6
Hello,7
Hello,8
Hello,9
Hello,10
Hello,11
Hello,12
Hello,13
Hello,14
[Loaded sun.reflect.ClassFileConstants from D:\jdk8\jre\lib\rt.jar]
[Loaded sun.reflect.AccessorGenerator from D:\jdk8\jre\lib\rt.jar]
[Loaded sun.reflect.MethodAccessorGenerator from D:\jdk8\jre\lib\rt.jar]
[Loaded sun.reflect.ByteVectorFactory from D:\jdk8\jre\lib\rt.jar]
[Loaded sun.reflect.ByteVector from D:\jdk8\jre\lib\rt.jar]
[Loaded sun.reflect.ByteVectorImpl from D:\jdk8\jre\lib\rt.jar]
[Loaded sun.reflect.ClassFileAssembler from D:\jdk8\jre\lib\rt.jar]
[Loaded sun.reflect.UTF8 from D:\jdk8\jre\lib\rt.jar]
[Loaded sun.reflect.Label from D:\jdk8\jre\lib\rt.jar]
[Loaded sun.reflect.Label$PatchInfo from D:\jdk8\jre\lib\rt.jar]
[Loaded java.util.ArrayList$Itr from D:\jdk8\jre\lib\rt.jar]
[Loaded sun.reflect.MethodAccessorGenerator$1 from D:\jdk8\jre\lib\rt.jar]
[Loaded sun.reflect.ClassDefiner from D:\jdk8\jre\lib\rt.jar]
[Loaded sun.reflect.ClassDefiner$1 from D:\jdk8\jre\lib\rt.jar]
[Loaded java.util.concurrent.ConcurrentHashMap$ForwardingNode from D:\jdk8\jre\lib\rt.jar]
[Loaded sun.reflect.GeneratedMethodAccessor1 from __JVM_DefineClass__]
Hello,15
[Loaded java.lang.Shutdown from D:\jdk8\jre\lib\rt.jar]
[Loaded java.lang.Shutdown$Lock from D:\jdk8\jre\lib\rt.jar]