反射性能分析和代理原理
反射为什么效率差?
一、结论
- Method#invoke 方法会对参数做装箱和拆箱操作
- 需要检查方法可见性
- 需要校验参数
- 反射方法难以内联
- JIT 无法优化
二、源码分析
1. 使用案例:
1 | public class RefTest { |
下面从 获取 Method 对象 开始分析,方法getMethod
/ getDeclaredMethod
都可以用于获取 Method 对象
2. getMethod 和 getDeclaredMethod 有什么区别?
1 | class Class { |
获取方法的流程分三步走:
- 检查方法权限
为什么getMethod
中checkMemberAccess
传入的是 Member.PUBLIC
,而 getDeclaredMethod
传入的是Member.DECLARED
?这两个值有什么区别呢?
1 | interface Member { |
这也是getMethod
和getDeclaredMethod
的区别。
获取方法 Method 对象
getMethod
中获取方法调用的是getMethod0
,而getDeclaredMethod
获取方法调用的是privateGetDeclaredMethods
,实际上而getMethod0
会递归查找父类的方法,最终会调用到privateGetDeclaredMethods
方法。
1 | /** |
返回方法的拷贝
为什么是返回拷贝?
3. getMethod 分析
流程一:检查方法权限做了什么事情?
1 | class Class { |
流程二:如何获取 Method 对象?
先看 getMethod0 :
1 | class Class { |
接着看 getMethodsRecursive 方法,是如何获取方法的:
1 | class Class { |
为什么通过名字和参数拿到的是一个Method列表?
编写 Java 代码时,同一个类是不能有方法名和方法参数都相同的方法
而实际上,在 JVM 中,一个方法签名是和 返回值、方法名、方法参数 三者相关的。
也就是说,在 JVM 中,可以存在 方法名和方法参数都相同,但是返回值不同的方法。
所以这里返回的是一个方法链表。
由 getMethodsRecursive 方法可知,最终还是会走到 privateGetDeclaredMethods 中:
1 | class Class { |
先看看 relectionData 方法:
1 | class Class { |
这就是 getMethod 方法的整个实现了。
再回过头看一下 getDeclaredMethod 方法的实现,通过 privateGetDeclaredMethods 获取方法以后,会通过 searchMethods 对方法进行筛选:
1 | public Method getDeclaredMethod(String name, Class<?>... parameterTypes) |
1 | class Class { |
流程三:Method#copy
1 | class Method { |
每一次返回的Method对象其实都是一个新的对象,但是:
- 新对象的root属性都指向源Method对象
- methodAccessor是所有对象共享的
4. invoke 分析
1 |
|
首次调用 MethodAccessor 为空,因此进入 acquireMethodAccessor() 中:
1 | private MethodAccessor acquireMethodAccessor() { |
之后便会执行 MethodAccessor 的invoke,那么 MethodAccessor 到底做了什么呢?
简单来说,MethodAccessor 做了两件事:
- 解析参数,解封操作
- 调用对象的方法
那么这里有两类MethodAccessor :
- 默认 Native 版本的 MethodAccessor 实现——NativeMethodAccessorImpl
- Java 版本的 MethodAccessor 实现—— MethodAccessorImpl
为什么会有两类?
因为跨越native边界会对优化有阻碍作用,它就像个黑箱一样让虚拟机难以分析也将其内联,于是运行时间长了之后反而是java版本的代码更快些。
Java 版本的 MethodAccessorImpl 调用效率比 Native 版本要快 20 倍以上,但是 Java 版本加载时要比 Native 多消耗 3-4 倍资源,所以默认会调用 Native 版本,如果调用次数超过 15 次以后,就会选择运行效率更高的 Java 版本。
1 | //java版本的 MethodAccessor,相当于执行 A.foo("aaa") |
常用代理技术
JDK代理
1 | //代理类反编译 |
Cglib代理
1 | //代理类反编译 |
Method 和 MethodProxy 有什么区别?
MethodProxy 是 cglib 为了避免反射使用的一种代替方式,但是功能是一样的。
cglib与jdk代理对比?
- JDK动态代理是实现了被代理对象的接口,Cglib是继承了被代理对象。
- JDK和Cglib都是在运行期生成字节码,JDK是直接写Class字节码,Cglib使用ASM框架写Class字节码,Cglib代理实现更复杂,生成代理类比JDK效率低。
- JDK调用代理方法,是通过反射机制调用,Cglib是通过FastClass机制直接调用方法,Cglib执行效率更高。