Java 是如何实现反射的?

方法的反射调用实现

  • Mehtod.invoke(): 方法的反射调用是委派给 MethodAccessor 接口来处理。该接口有两个实现类:DelegatingMethodAccessorImplNativeMethodAccessorImpl
    • 委托实现:即 DelegatingMethodAccessorImpl,使用 Inflation 实现方法调用。
      • NativeMethodAccessorImpl: 当某个反射调用的调用次数在15(该阈值可以通过 -Dsun.reflect.infationThreshold= 来调整)次之下时,采用本地实现
      • 当反射调用的调用次数达到15次时,开始动态生成字节码,并将委派实现的委派对象切换到动态实现

反射调用的开销

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值