反射底层原理
# 简介
反射机制是在运行状态中,对于任意一个类,都能够获得这个类的所有属性和方法,对于任意一个对象都能够调用它的任意一个属性和方法。这种在运行时动态的获取信息以及动态调用对象的方法的功能称为 Java 的反射机制。
# 原理
通常情况下我们是这么使用反射的:
Method method = XXX.class.getDeclaredMethod(xx,xx);
method.invoke(target,params)
2
# Method 获取
要调用首先要获取Method
,而获取Method
的逻辑是通过Class
这个类来的,而关键的几个方法和属性如下:
class Class{
SoftReference<ReflectionData<T>> reflectionData;
public Method getDeclaredMethod(String name, Class<?>... parameterTypes);
private static Method searchMethods(Method[] methods,String name, Class<?>[] parameterTypes);
private Method[] privateGetDeclaredMethods(boolean publicOnly);
}
2
3
4
5
6
7
其中privateGetDeclaredMethods
方法从缓存或JVM中获取该Class
中申明的方法列表,searchMethods
方法将从返回的方法列表里找到一个匹配名称和参数的方法对象。privateGetDeclaredMethods
中获取方法列表主要就是靠reflectionData
。
# reflectionData
这里主要存的是每次从jvm里获取到的一些类属性,比如方法,字段等。
这个属性主要是SoftReference
的,也就是在某些内存比较苛刻的情况下是可能被回收的,不过正常情况下可以通过-XX:SoftRefLRUPolicyMSPerMB这个参数来控制回收的时机,一旦时机到了,只要GC发生就会将其回收,那回收之后意味着再有需求的时候要重新创建一个这样的对象,同时也需要从JVM里重新拿一份数据,那这个数据结构关联的Method,Field字段等都是重新生成的对象。
# copy()方法
在获取到Method
对象之后,并不是直接返回,而是copy
一份对象。
这里的root
可看作是每一个方法唯一对应的Method
,以后产生的每个Method
都是从这里复制出去的。同所有的Method
的methodAccessor
都指向同一个。
# Method 使用
有了Method
之后,那就可以调用其invoke
方法了,那先看看Method
的几个关键信息
class Method{
public Object invoke(Object obj, Object... args);
private Method root;
private volatile MethodAccessor methodAccessor;
}
2
3
4
5
root
属性其实上面已经说了,主要指向缓存里的Method
对象,也就是当前这个Method对象其实是根据root
这个Method构建出来的,因此存在一个root Method
派生出多个Method
的情况。
methodAccessor
这个很关键了,其实Method.invoke
方法就是调用methodAccessor
的invoke
方法,methodAccessor
这个属性如果root
本身已经有了,那就直接用root
的methodAccessor
赋值过来,否则的话就创建一个
# MethodAccessor
methodAccessor
本身只是一个接口
其实现主要有三种:
- DelegatingMethodAccessorImpl
- NativeMethodAccessorImpl
- GeneratedMethodAccessorXXX
其中DelegatingMethodAccessorImpl
是最终注入给Method
的methodAccessor
的,也就是某个Method
的所有的invoke
方法都会调用到这个DelegatingMethodAccessorImpl.invoke
,正如其名一样的,是做代理的,就是为了下面切换不同调用类使用的。所以最终生成的MethodAccessor
就两种。
如果是NativeMethodAccessorImpl
,那顾名思义,该实现主要是native实现的,而GeneratedMethodAccessorXXX
是为每个需要反射调用的Method
动态生成的类,后的XXX是一个数字,不断递增的
并且所有的方法反射都是先走NativeMethodAccessorImpl
,默认调了15次之后,才生成一个GeneratedMethodAccessorXXX
类,生成好之后就会走这个生成的类的invoke方法了。那么怎么定义是15次?到了15次又是怎么切换类的呢?为什么又需要切换调用的invoke类呢?
在生成methodAccessor
的类ReflectionFactory
中定义了阈值15
class ReflectionFactory{
private static int inflationThreshold = 15;
}
2
3
这个15当然也不是一尘不变的,我们可以通过-Dsun.reflect.inflationThreshold=xxx
来指定,我们还可以通过-Dsun.reflect.noInflation=true
来直接绕过上面的15次NativeMethodAccessorImpl
调用,和-Dsun.reflect.inflationThreshold=0
的效果一样的
这个值在NativeMethodAccessorImpl
调用invoke
方法时候会判断是否大于15,超过以后会生成GeneratedMethodAccessorXXX
,其实就是直接调用目标对象的具体方法了,和正常的方法调用没什么区别。这样下次Method.invoke
就会调到这个新创建的MethodAccessor里了。
至于为什么必须走 NativeMethodAccessorImp.invoke
,源码给的解释是通过加载字节码以实现Method.invoke()
和Constructor.newInstance()
成本是第一次通过本机代码调用(本机代码应该是native方法)的成本的3-4倍。这会增加某些密集使用反射的应用程序的启动时间(但每个类仅一次)为了避免这种情况,我们在方法和构造函数的最初几次调用中重用了现有的JVM入口点,然后才是基于字节码的实现。
参考: 1、深入分析Java方法反射的实现原理 (opens new window) 2、假笨说-从一起GC血案谈到反射原理 (opens new window)