0%

java 反射机制与代码走读

本文介绍 Java 的反射机制,源码走读

什么是反射

先看正反的两个例子

正向的例子:已知一个类,直接进行实例化,使用类对象进行操作

1
2
Dog dog = new Dog();
dog.setFood("meat");

反射的例子:不知道是什么类,无法使用 new 关键字直接创建对象

1
2
3
4
5
Class clz = Class.forName("org.example.Dog");
Method method = clz.getMethod("setFood", String.class);
Constructor constructor = clz.getConstructor();
Object obj = constructor.newInstance();
method.invoke(obj, "meat");

你可能会有疑问,这两个例子不就是写法不同吗,都是已知这个类是什么;在反射的例子中,只不过是用反射的形式创建对象和调用方法。看上去是这样的,不过在实际使用中,是在程序运行过程中通过字符串变量值,才知道是什么类

所以反射的概念可以理解为:运行时才知道要操作的类是什么,在运行时构造类的对象,调用其方法

反射的步骤

一般情况下,可以归纳为下面几个步骤:

  1. 获取类的 Class 对象实例

    Class clz = Class.forName("...");

  2. 根据 Class 对象实例获取 Constructor 对象

    Constructor constructor = clz.getConstructor();

  3. 使用 Constructor 对象的 newInstance 方法获取

    Object obj = constructor.newInstance();

  4. 获取对象实例方法

    Method method = clz.getMethod("eat", String.class);

  5. 调用对象实例方法

    method.invoke(obj, "meat");

下面就这五个步骤,进行源码走读

源码走读

Class.forName

1
2
3
4
5
6
@CallerSensitive
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}

根据类全路径获取类对象

clz.getMethod

java.lang.Class#getMethod

1
2
3
4
5
6
7
8
9
10
@CallerSensitive
public Method getMethod(String name, Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
Method method = getMethod0(name, parameterTypes, true);
if (method == null) {
throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
}
return method;
}
方法 功能
checkMemberAccess 检查调用类是否有访问权限,默认是可以访问
getMethod0 根据方法名和参数列表查询目标方法
1
2
3
4
5
6
7
8
9
10
private Method getMethod0(String name, Class<?>[] parameterTypes, boolean includeStaticMethods) {
MethodArray interfaceCandidates = new MethodArray(2);
Method res = privateGetMethodRecursive(name, parameterTypes, includeStaticMethods, interfaceCandidates);
if (res != null)
return res;

// Not found on class or superclass directly
interfaceCandidates.removeLessSpecifics();
return interfaceCandidates.getFirst(); // may be null
}

privateGetMethodRecursive:递归的根据方法名和参数类型寻找方法

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
private Method privateGetMethodRecursive(String name,
Class<?>[] parameterTypes,
boolean includeStaticMethods,
MethodArray allInterfaceCandidates) {
// Must _not_ return root methods
Method res;
// Search declared public methods
if ((res = searchMethods(privateGetDeclaredMethods(true),
name,
parameterTypes)) != null) {
if (includeStaticMethods || !Modifier.isStatic(res.getModifiers()))
return res;
}
// Search superclass's methods
if (!isInterface()) {
Class<? super T> c = getSuperclass();
if (c != null) {
if ((res = c.getMethod0(name, parameterTypes, true)) != null) {
return res;
}
}
}
// Search superinterfaces' methods
Class<?>[] interfaces = getInterfaces();
for (Class<?> c : interfaces)
if ((res = c.getMethod0(name, parameterTypes, false)) != null)
allInterfaceCandidates.add(res);
// Not found
return null;
}

privateGetMethodRecursive 流程图如下

反射流程图.drawio

privateGetMethodRecursive 实现分析:

方法 功能
privateGetDeclaredMethods 返回root方法数组
searchMethods(Method[] methods, String name, Class<?>[] parameterTypes) 在输入的方法列表中,查找方法名为name,参数为parameterTypes 的方法

privateGetDeclaredMethods:

从缓存中获取方法,如果缓存中没有,从 VM 中获取

类中的每个方法最初都是向 VM 请求获取的,从 VM 获取的这些 Method 对象,称为类中方法对应的 Method Root(根方法对象),最终找到的方法,是 Method Root 拷贝的副本 Method 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 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) {
checkInitted();
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;
}

涉及的方法分析:

方法 功能
checkInitted() 读VM变量初始化参数(是否要读缓存 useCaches,是否完成初始化 initted)
reflectionData() 懒加载反射数据:如果缓存中存在反射数据,则返回;缓存中没有,创建一个空的反射数据,用于从VM加载后写缓存
getDeclaredMethods0(boolean) native方法,从VM中获取当前类的(公开/全部)方法
Reflection.filterMethods() 一些 unsafe 方法需要被过滤

展开:reflectionData

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Lazily create and cache ReflectionData
private ReflectionData<T> reflectionData() {
SoftReference<ReflectionData<T>> reflectionData = this.reflectionData;
int classRedefinedCount = this.classRedefinedCount;
ReflectionData<T> rd;
if (useCaches &&
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);
}

有了 privateGetDeclaredMethods 获取的公开方法,放到 searchMethods 中,进行方法名和参数比较,如果最后发现 res 为空,说明没有找到目标方法,如果 res 不为空,要拷贝 Root 方法,返回 root 方法的副本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private static Method searchMethods(Method[] methods,
String name,
Class<?>[] parameterTypes)
{
Method res = null;
String internedName = name.intern();
for (int i = 0; i < methods.length; i++) {
Method m = methods[i];
if (m.getName() == internedName
&& arrayContentsEq(parameterTypes, m.getParameterTypes())
&& (res == null
|| res.getReturnType().isAssignableFrom(m.getReturnType())))
res = m;
}

return (res == null ? res : getReflectionFactory().copyMethod(res));
}

展开:getReflectionFactory().copyMethod(res) —— 根方法的拷贝

1
2
3
public Method copyMethod(Method arg) {
return arg.copy();
}

跟进去会调用:java.lang.reflect.Method#copy

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

这里可以看到,拷贝的一定是根方法对象,然后用根方法对象(this)的所有参数创建了一个新的方法对象 res,res 的 root 指向了根方法对象(this),并设置 methodAccessor

问题:

  1. 为什么要有 Method Root?
  2. Method Root为什么不能传播到外部,必须经过复制
  3. 为什么 copy 的方法和根方法要用相同的 methodAccessor?
  4. methodAccessor 是做什么的?

method.invoke

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

把 method.invoke 分为两个部分:Part1. acquireMethodAccessor 和 Part2. ma.invoke

Part1. acquireMethodAccessor

检查方法对象的 methodAccessor(下面简称 ma) 是否为空,如果为空,创建一个 ma

image-20230323233830053

追踪 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;
}

解读:先找父方法的 ma,如果父方法没有 ma,为父方法和自己创建 ma,返回这个 ma

追踪:reflectionFactory.newMethodAccessor(this)

1
2
3
4
5
6
7
8
9
10
11
public MethodAccessor newMethodAccessor(Method var1) {
checkInitted();
if (noInflation && !ReflectUtil.isVMAnonymousClass(var1.getDeclaringClass())) {
return (new MethodAccessorGenerator()).generateMethod(var1.getDeclaringClass(), var1.getName(), var1.getParameterTypes(), var1.getReturnType(), var1.getExceptionTypes(), var1.getModifiers());
} else {
NativeMethodAccessorImpl var2 = new NativeMethodAccessorImpl(var1);
DelegatingMethodAccessorImpl var3 = new DelegatingMethodAccessorImpl(var2);
var2.setParent(var3);
return var3;
}
}

解读:var1 为子方法,首先在 checkInitted() 中进行初始化:ReflectionFactory.noInflation 和 ReflectionFactory.inflationThreshold 两个参数,其中 noInflation 默认为 false 表示有膨胀机制,膨胀机制在后面会提到

checkInitted 后,默认 noInflation 是 false,则走到第二个分支,这里创建了两个对象:NativeMethodAccessorImpl 和 DelegatingMethodAccessorImpl类型,这两个类型都是 MethodAccessor 的实现类,var3 是 var2 的 parent,最后返回 var3。类之间关系如下:

methodAccessor.drawio

var1, var2, var3 的引用关系如下:

var123.drawio

追踪 setMethodAccessor (java.lang.reflect.Method#setMethodAccessor)

1
2
3
4
5
6
7
8
9
// Sets the MethodAccessor for this Method object and
// (recursively) its root
void setMethodAccessor(MethodAccessor accessor) {
methodAccessor = accessor;
// Propagate up
if (root != null) {
root.setMethodAccessor(accessor);
}
}

使用 reflectionFactory.newMethodAccessor(this) 创建了新的 ma 后,将这个 ma 递归的向上赋值给父方法,使得该方法类以及向上所有的方法类的 ma 都是一个(为什么要这样做呢?)

Part2. ma.invoke

image-20230323233748550

通过 Part1. acquireMethodAccessor 获取 ma 对象后,调用 ma 的 invoke 方法,obj 是要反射的实例,args 是方法参数;我们知道 ma 是一个 DelegatingMethodAccessorImlp 类型,追踪调用:

1
2
3
public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
return this.delegate.invoke(var1, var2);
}

this.delegate 是 NativeMethodAccessorImpl 类型,追踪调用:

1
2
3
4
5
6
7
public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
if (++this.numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(this.method.getDeclaringClass())) {
MethodAccessorImpl var3 = (MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(this.method.getDeclaringClass(), this.method.getName(), this.method.getParameterTypes(), this.method.getReturnType(), this.method.getExceptionTypes(), this.method.getModifiers());
this.parent.setDelegate(var3);
}
return invoke0(this.method, var1, var2);
}

解读:如果调用次数大于阈值,生成另一个 ma 对象,并将原来 DelegatingMethodAccessorImpl 对象中的 delegate 属性指向最新的 ma 对象;本次依旧会用 NativeMethodAccessorImpl 的 invoke0 方法,不过下次调用会就不会再调用 NativeMethodAccessorImpl 的invoke() 方法了

1
private static native Object invoke0(Method var0, Object var1, Object[] var2);

下面是执行 invoke 的时序图:

invoke.drawio

当调用次数大于阈值(默认15),会将 DelegatingMethodAccessorImpl 的 delegate 属性更改为一个 GeneratedMethodAccessor,不去用 NativeMethodAccessorImpl 的 invoke0 方法;这个 GeneratedMethodAccessor.invoke 的具体实现,我没有找到,GeneratedMethodAccessor 的类文件也没有找到,这个 GeneratedMethodAccessor 是怎么来的,还要再讨论分析

image-20230324153440404

根据注释所述:如注释所述,实际的MethodAccessor实现有两个版本,一个是Java实现的(GeneratedMethodAccessor.invoke),另一个是native code(NativeMethodAccessorImpl.invoke0)实现的。Java 实现的版本在初始化时需要较多时间,但长久来说性能较好;native 版本正好相反,启动时相对较快,但运行时间长了之后速度就比不过 Java 版了,为了权衡这两个版本的性能,引入了膨胀机制

所谓膨胀机制是指:一开始先使用 native 的 ma 对象,等 native ma 的调用次数达到了 ReflectionFactory.inflationThreshold 设定的阈值后,动态生成 java 版本的 ma 对象来调用

实验分析 invoke 膨胀机制

下面进行实验验证膨胀机制

实验设计

实验目的:比较 GeneratedMethodAccessor.invoke 和 NativeMethodAccessorImpl.invoke0 两种 Invoke 方法的执行时间和内存使用情况

通过 -Dsun.reflect.inflationThreshold=0 或 很大整数(保证实验中不会走到优化的分支)控制使用哪种方法:sun.reflect.inflationThreshold=0 使用 GeneratedMethodAccessor.invoke,sun.reflect.inflationThreshold=很大整数时使用 NativeMethodAccessorImpl.invoke0

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

private static final int RUN_TIME = 1;

private static final int TURN_NUM = 1000;

public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
String s = System.getProperty("sun.reflect.inflationThreshold");
System.out.println("sun.reflect.inflationThreshold: " + s);
Class<?> clz = Class.forName("org.example.reflect.Dog");
Constructor<?> constructor = clz.getConstructor();
Object obj = constructor.newInstance();
Method method = clz.getMethod("eat", String.class);
for (int i = 0; i < TURN_NUM; i++) {
costTime(obj, method);
}
}

private static void costTime(Object obj, Method method) throws InvocationTargetException, IllegalAccessException {
Long start = System.nanoTime();
for (int i = 0; i < RUN_TIME; i++) {
Object res = method.invoke(obj, "meat");
}
Long end = System.nanoTime();
System.out.println("cost Time: " + (end - start) + " ns");
}

}
耗时比较

下面是第n次执行和耗时(单位:ns)的表格,比较发现:第一次运行,GeneratedMethodAccessor.invoke 耗时是 Native Invoke 的20倍,从第2次开始,两者耗时均下降明显,从18-25次,GeneratedMethodAccessor.invoke 耗时均低于 Native Invoke

image-20230325114624920

运行300次-350次的耗时比较,GeneratedMethodAccessor.invoke 平均耗时在625ns,Native invoke 平均耗时在 910 ns

image-20230325121200359

运行960次-1000次的耗时比较,GeneratedMethodAccessor.invoke 平均耗时在125ns,Native invoke 平均耗时在 375 ns

image-20230325115818285
内存比较

使用 visualvm 工具查看-Dsun.reflect.inflationThreshold=0 或 100000两种情况下的堆空间使用率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ReflectTest {
private static final int TURN_NUM = 10000000;

public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, InterruptedException {
Thread.sleep(15000);
for (int i = 0; i < TURN_NUM; i++) {
Class<?> clz = Class.forName("org.example.reflect.Dog");
Constructor<?> constructor = clz.getConstructor();
Object obj = constructor.newInstance();
Method method = clz.getMethod("eat", String.class);
method.invoke(obj, "meat");
}
Thread.sleep(15000);
}

}

下图为使用 GeneratedMethodAccessor.invoke 的堆空间使用情况,invoke 执行过程中最大的 Used heap = 189.93M

image-20230325133027796

下图为使用 Native Invoke 的堆空间使用情况,invoke 执行过程中最大 Used heap = 90.05M

image-20230325133056066

可以发现 GeneratedMethodAccessor.invoke 比 native invoke 占用更多的堆空间

问题

下面来看下走读过程中遇到的几个问题:

问题:

  1. methodAccessor 是做什么的?

    答:用于 invoke 调用指定方法的

  2. 为什么要有 Method Root?

    答:为了让同一个方法拷贝出来的多个 Method 对象,共享 method Accessor 对象;当第一次调用 method.invoke 时,没有 method Accessor 对象,则取 root 的 Method Accessor,如果 root 的 method Accessor 为空,则新创建一个 method Accessor,并给 root 赋值。又因为一个 Method 对象,一定是从 Method Root 拷贝来的,在拷贝过程中,会赋 root.methodAccessor 值(见java.lang.reflect.Method#copy),从而达到了共享 method Accessor 的效果

  3. Method Root为什么不能传播到外部,必须经过复制,为什么 Method 对象本身不能共享

    答:为了保持方法原有的样子,因为用户可以对拷贝出去的 Method 对象setAccessible,如果修改了根方法的 accessible,后面拷贝的方法都被修改了 setAccessible;同理,Method 对象本身也不能共享一个,因为可能只修改某个Method的属性(个人理解)

  4. 为什么 copy 的方法和根方法要用相同的 methodAccessor?

    答:首先 methodAccessor 是用来执行指定方法的,从根方法拷贝出的多个副本,共享一个 methodAccessor,节省内存空间

个人感受:第一遍看的时候,先看到 Method Root,拷贝Method Root,后看到 methodAccessor,这样无法理解为什么要这么做;第二编看的时候,大概理解了 methodAccessor 的作用后,再看 Method Root,又有了新的认识,所以先理解MethodAccessor,对整个流程的理解比较重要

总结

本文介绍了什么是反射,进行了反射源码走读,主要是 clz.getMethod 和 method.invoke 两个函数的代码,以及其调用追踪,过了下整个反射过程,发现了两个重要的概念:Method Root、Method Accessor,通过代码分析它们的作用,以及膨胀机制,通过实验对膨胀机制在运行时间和内存上进行比较;最后对阅读过程中发现的几个问题进行讨论

参考

大白话说Java反射:入门、使用、原理

java反射源码分析,思路超清晰

JAVA深入研究——Method的Invoke方法