链接:https://blog.csdn.net/FU250/article/details/90231983
https://juejin.im/post/6889833658669072397
反射机制可以让你在程序运行时,拿到任意一个类的属性和方法并调用它。
反射(Reflection)是一种在 运行时 动态访问类型信息 的机制
什么是反射
也就是说反射是一种特性,它允许正在运行的 Java 程序观测甚至是修改程序的动态行为。
什么情况下可以用到反射
1.当前类方法不允许使用的时候(案例,透明Activity,修改switch的宽度)
2.根据配置文件加载加载不同的类
实现原理
采用委派实现,便是为了能够在本地实现以及动态实现中切换。
1.通过本地方法来实现反射调用(C++ 实现)
2.Java 的反射调用机制还设立了另一种动态生成字节码的实现,直接使用 invoke 指令来调用目标方法
重要
动态实现和本地实现相比,其运行效率要快上 20 倍 。这是因为动态实现无需经过 Java 到 C++ 再到 Java 的切换,但由于生成字节码十分耗时,仅调用一次的话,反而是本地实现要快上 3 到 4 倍 在生产环境中,往往拥有多个不同的反射调用,对应多个动态实现,但是可能会造成无法内联的情况
三、反射的开销
方法的反射调用会带来不少性能开销,原因主要有三个:变长参数方法导致的 Object 数组,基本类型的自动装箱、拆箱,还有最重要的方法内联。
1、由于 Method.invoke 是一个变长参数方法,在字节码层面它的最后一个参数会是 Object 数组。Java 编译器会在方法调用处生成一个长度为传入参数数量的 Object 数组,并将传入参数一一存储进该数组中。
2、由于 Object 数组不能存储基本类型,Java 编译器会对传入的基本类型参数进行自动装箱。
3、Class.forName 会调用本地方法,Class.getMethod 则会遍历该类的公有方法。如果没有匹配到,它还将遍历父类的公有方法。可想而知,这两个操作都非常费时。
import java.lang.reflect.Method;
public class Test {
public static void target(int i) {
// 空方法
}
public static void main(String[] args) throws Exception {
Class<?> klass = Class.forName("Test");
Method method = klass.getMethod("target", int.class);
Object[] arg = new Object[1]; // 在循环外构造参数数组
arg[0] = 128;
long current = System.currentTimeMillis();
for (int i = 1; i <= 2_000_000_000; i++) {
if (i % 100_000_000 == 0) {
long temp = System.currentTimeMillis();
System.out.println(temp - current);
current = temp;
}
method.invoke(null, arg); // 在调用 方法实现的,使用的是变长参数
}
}
}
Object[] arg = new Object[1]; // 在循环外构造参数数组
arg[0] = 128;
long current = System.currentTimeMillis();
for (int i = 1; i <= 2_000_000_000; i++) {
if (i % 100_000_000 == 0) {
long temp = System.currentTimeMillis();
System.out.println(temp - current);
current = temp;
}
method.invoke(null, arg);
}
}
}
四、方法内联
方法内联指的是在编译过程中遇到方法调用时,将目标方法的方法体纳入编译范围之中,并取代原方法调用的优化手段,采用少干活的方式来提高效率,直接将对应方法的字节码内联过来,省下了记录切换上下文环境的时间和空间 以 getter/setter 为例,如果没有方法内联,在调用 getter/setter 时,程序需要保存当前方法的执行位置,创建并压入用于 getter/setter 的栈帧、访问字段、弹出栈帧,最后再恢复当前方法的执行。而当内联了对 getter/setter 的方法调用后,上述操作仅剩字段访问。
五、逃逸分析
编译器可以根据逃逸分析的结果进行诸如锁消除(比如synchronized(new Object()))、栈上分配(方法退出后直接弹出,无须借助垃圾回收器处理)以及标量替换的优化(原本对对象的字段读取-存储替换为局部变量的读取-存储)
方法内联失效往往会伴随着编译器会认为方法的调用者以及参数是逃逸的,因为对于方法未被内联的方法调用,即时编译器会将其当成未知代码,毕竟它无法确认此方法调用会不会将调用者或所传入的参数存储至堆中。