本文介绍 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" );
你可能会有疑问,这两个例子不就是写法不同吗,都是已知这个类是什么;在反射的例子中,只不过是用反射的形式创建对象和调用方法。看上去是这样的,不过在实际使用中,是在程序运行过程中通过字符串变量值,才知道是什么类
所以反射的概念可以理解为:运行时才知道要操作的类是什么,在运行时构造类的对象,调用其方法
反射的步骤
一般情况下,可以归纳为下面几个步骤:
获取类的 Class 对象实例
Class clz = Class.forName("...");
根据 Class 对象实例获取 Constructor 对象
Constructor constructor = clz.getConstructor();
使用 Constructor 对象的 newInstance 方法获取
Object obj = constructor.newInstance();
获取对象实例方法
Method method = clz.getMethod("eat", String.class);
调用对象实例方法
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; interfaceCandidates.removeLessSpecifics(); return interfaceCandidates.getFirst(); }
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) { Method res; if ((res = searchMethods(privateGetDeclaredMethods(true ), name, parameterTypes)) != null ) { if (includeStaticMethods || !Modifier.isStatic(res.getModifiers())) return res; } if (!isInterface()) { Class<? super T> c = getSuperclass(); if (c != null ) { if ((res = c.getMethod0(name, parameterTypes, true )) != null ) { return res; } } } Class<?>[] interfaces = getInterfaces(); for (Class<?> c : interfaces) if ((res = c.getMethod0(name, parameterTypes, false )) != null ) allInterfaceCandidates.add(res); 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 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; } 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 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; } 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 () { 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 ; res.methodAccessor = methodAccessor; return res; }
这里可以看到,拷贝的一定是根方法对象,然后用根方法对象(this)的所有参数创建了一个新的方法对象
res,res 的 root 指向了根方法对象(this),并设置 methodAccessor
问题:
为什么要有 Method Root?
Method Root为什么不能传播到外部,必须经过复制
为什么 copy 的方法和根方法要用相同的 methodAccessor?
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; 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 private MethodAccessor acquireMethodAccessor () { MethodAccessor tmp = null ; if (root != null ) tmp = root.getMethodAccessor(); if (tmp != null ) { methodAccessor = tmp; } else { 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 void setMethodAccessor (MethodAccessor accessor) { methodAccessor = accessor; 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
占用更多的堆空间
问题
下面来看下走读过程中遇到的几个问题:
问题:
methodAccessor 是做什么的?
答:用于 invoke 调用指定方法的
为什么要有 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
的效果
Method Root为什么不能传播到外部,必须经过复制,为什么 Method
对象本身不能共享
答:为了保持方法原有的样子,因为用户可以对拷贝出去的 Method
对象setAccessible,如果修改了根方法的
accessible,后面拷贝的方法都被修改了 setAccessible;同理,Method
对象本身也不能共享一个,因为可能只修改某个Method的属性(个人理解)
为什么 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方法