方法的反射调用实现
Mehtod.invoke()
: 方法的反射调用是委派给MethodAccessor
接口来处理。该接口有两个实现类:DelegatingMethodAccessorImpl
和NativeMethodAccessorImpl
- 委托实现:即
DelegatingMethodAccessorImpl
,使用 Inflation 实现方法调用。NativeMethodAccessorImpl
: 当某个反射调用的调用次数在15(该阈值可以通过-Dsun.reflect.infationThreshold=
来调整)次之下时,采用本地实现- 当反射调用的调用次数达到15次时,开始动态生成字节码,并将委派实现的委派对象切换到动态实现
- 委托实现:即
反射调用的开销
Class.forName
会调用本地方法;Class.getMethod
会遍历该类的公有方法,如果没有匹配到,它还将遍历父类的公有方法;- 以
getMethod
为代表的查找方法操作,会返回查找得到结果的一份拷贝。因此我们应当避免在热点代码中使用返回Method
数据的getMethods
或者getDelaredMethods
方法,以减少不必要的堆空间消耗 - 在实践中,我们往往会在应用程序缓存
Class.forName
和Class.getMethod
的结果。
- 以
Method.invoke()
性能消耗:方法的反射调用会带来不少性能开销,原因主要有三个:变长参数方法导致的 Object 数组,基本类型的自动装箱、拆箱,还有最重要的方法内联。- 由于该方法是一个变长参数方法,在字节码层面,它的最后一个参数会是一个
Object[]
数组。Java 编译器会在方法调用处生成一个长度为传入参数数量的Object[]
数组,并将传入参数一一存储到该数组中。 - 由于
Object[]
数组不能存储基本类型,Java 编译器会对传入的基本类型参数进行自动装箱。所以应当在调用该方法前,将参数的基本类型进行手动装箱处理,避免在循环调用过程中的处理开销。 - 不建议在使用
Method.invoke()
方法前自行构建 Object 数组,原因如下。如果一个对象不逃逸,那么即时编译器可以选择栈分配甚至是虚拟分配,也就是不占用堆内存空间。如果在循环外新建数组,即时编译器无法确认这个数组会不会被中途更改,因此无法优化掉访问数组的操作。
- 由于该方法是一个变长参数方法,在字节码层面,它的最后一个参数会是一个