1.反射调用的实现
本地实现
package com.sz.simple;
import java.lang.reflect.Method;
public class Test {
public static void target() {
new Exception("#").printStackTrace();
}
public static void main(String[] args) throws Exception {
Test test = new Test();
Class<?> klass = Class.forName("com.sz.simple.Test");
Method method = klass.getMethod("target");
method.invoke(test,null);
}
}
如上代码,当执行main方法时,首先会通过全限定类名获取到该类的字节码对象,然后获取到Method对象后反射调用执行目标方法。打印方法执行时的堆栈轨迹如下图
反射类调用委派类的invoke方法,然后委派类调用本地方法的invoke方法,最后执行目标方法
Java反射方法 ->java委派方法 -> c++本地方法 -> java目标方法
由于 java 到 c++ 方法 再到 java 方法的这一过程非常耗时,因此 委派类除了可以委派给本地方法实现外,还能委派给动态方法实现
动态实现
将上述代码修改为如下所示
package com.sz.simple;
import java.lang.reflect.Method;
public class Test {
public static void target(int i) {
new Exception("#"+i).printStackTrace();
}
public static void main(String[] args) throws Exception {
Test test = new Test();
Class<?> klass = Class.forName("com.sz.simple.Test");
for (int i = 0; i < 17; i++) {
Method method = klass.getMethod("target",int.class);
method.invoke(test,i);
}
}
}
java -verbose:class com.sz.simple.Test
对比第15次和第16次打印,你会发现,在第16次执行的时候,委派类委派给了GeneratedMethodAccessor,采用了动态实现。
改为动态实现的 15 临界值我们可用通过修改 Dsun.reflect.inflationThreshold 的值修改,如果你的项目中大量使用到了反射去调用方法,而不想产生大量动态生成类,可用设置 Dsun.reflect.inflationThreshold = int.max
如果你不想使用本地实现去调用目标方法,你可以执行时添加如下的参数
-Dsun.reflect.noInflation=true
2. 两种反射调用的实现效率
动态实现和本地实现相比,其运行效率要快上 20 倍 [2] 。这是因为动态实现无需经过 Java 到 C++ 再到 Java 的切换,但由于生成字节码十分耗时,仅调用一次的话,反而是本地实现要快上 3 到 4 倍 [3]。