• 新版网站前后台即将上线,2019年将致力于提高文章质量,加大原创力度,打造一个更加舒适的阅读体验!
  • 极客文库小编@勤劳的小蚂蚁,为您推荐每日资讯,欢迎关注!
  • 新版网站前后台即将上线,2019年将致力于提高文章质量,加大原创力度,打造一个更加舒适的阅读体验!
  • 如果有任何体验不佳的地方,欢迎向客服反馈!

深入分析 Java 方法反射的实现原理

方法反射实例

public class ReflectCase {
 
    public static void main(String[] args) throws Exception {
        Proxy target = new Proxy();
        Method method = Proxy.class.getDeclaredMethod(“run”);
        method.invoke(target);
    }
 
    static class Proxy {
        public void run() {
            System.out.println(“run”);
        }
    }
}

通过 Java 的反射机制,可以在运行期间调用对象的任何方法,如果大量使用这种方式进行调用,会有性能或内存隐患么?为了彻底了解方法的反射机制,只能从底层代码入手了。

Method 获取

调用 Class 类的 getDeclaredMethod 可以获取指定方法名和参数的方法对象 Method。

  • getDeclaredMethod


其中privateGetDeclaredMethods 方法从缓存或 JVM 中获取该 Class 中申明的方法列表,searchMethods 方法将从返回的方法列表里找到一个匹配名称和参数的方法对象

  • searchMethods


如果找到一个匹配的 Method,则重新 copy 一份返回,即 Method.copy()方法


所次每次调用 getDeclaredMethod 方法返回的 Method 对象其实都是一个新的对象,且新对象的 root 属性都指向原来的 Method 对象,如果需要频繁调用,最好把 Method 对象缓存起来。

  • privateGetDeclaredMethods

从缓存或 JVM 中获取该 Class 中申明的方法列表,实现如下:


其中 reflectionData()方法实现如下:


这里有个比较重要的数据结构 ReflectionData,用来缓存从 JVM 中读取类的如下属性数据:


从 reflectionData()方法实现可以看出:reflectionData 对象是 SoftReference 类型的,说明在内存紧张时可能会被回收,不过也可以通过-XX:SoftRefLRUPolicyMSPerMB 参数控制回收的时机,只要发生 GC 就会将其回收,如果 reflectionData 被回收之后,又执行了反射方法,那只能通过 newReflectionData 方法重新创建一个这样的对象了,newReflectionData 方法实现如下:


通过 unsafe.compareAndSwapObject 方法重新设置 reflectionData 字段;

在 privateGetDeclaredMethods 方法中,如果通过 reflectionData()获得的 ReflectionData 对象不为空,则尝试从 ReflectionData 对象中获取 declaredMethods 属性,如果是第一次,或则被 GC 回收之后,重新初始化后的类属性为空,则需要重新到 JVM 中获取一次,并赋值给 ReflectionData,下次调用就可以使用缓存数据了。

Method 调用

获取到指定的方法对象 Method 之后,就可以调用它的 invoke 方法了,invoke 实现如下:


应该注意到:这里的 MethodAccessor 对象是 invoke 方法实现的关键,一开始 methodAccessor 为空,需要调用 acquireMethodAccessor 生成一个新的 MethodAccessor 对象,MethodAccessor 本身就是一个接口,实现如下:


在 acquireMethodAccessor 方法中,会通过 ReflectionFactory 类的 newMethodAccessor 创建一个实现了 MethodAccessor 接口的对象,实现如下:


在 ReflectionFactory 类中,有 2 个重要的字段:noInflation(默认 false)和 inflationThreshold(默认 15),在 checkInitted 方法中可以通过-Dsun.reflect.inflationThreshold=xxx 和-Dsun.reflect.noInflation=true 对这两个字段重新设置,而且只会设置一次;

如果 noInflation 为 false,方法 newMethodAccessor 都会返回 DelegatingMethodAccessorImpl 对象,DelegatingMethodAccessorImpl 的类实现。


其实,DelegatingMethodAccessorImpl 对象就是一个代理对象,负责调用被代理对象 delegate 的 invoke 方法,其中 delegate 参数目前是 NativeMethodAccessorImpl 对象,所以最终 Method 的 invoke 方法调用的是 NativeMethodAccessorImpl 对象 invoke 方法,实现如下:


这里用到了 ReflectionFactory 类中的 inflationThreshold,当 delegate 调用了 15 次 invoke 方法之后,如果继续调用就通过 MethodAccessorGenerator 类的 generateMethod 方法生成 MethodAccessorImpl 对象,并设置为 delegate 对象,这样下次执行 Method.invoke 时,就调用新建的 MethodAccessor 对象的 invoke()方法了。

这里需要注意的是:
generateMethod 方法在生成 MethodAccessorImpl 对象时,会在内存中生成对应的字节码,并调用 ClassDefiner.defineClass 创建对应的 class 对象,实现如下:


在 ClassDefiner.defineClass 方法实现中,每被调用一次都会生成一个 DelegatingClassLoader 类加载器对象。


这里每次都生成新的类加载器,是为了性能考虑,在某些情况下可以卸载这些生成的类,因为类的卸载是只有在类加载器可以被回收的情况下才会被回收的,如果用了原来的类加载器,那可能导致这些新创建的类一直无法被卸载,从其设计来看本身就不希望这些类一直存在内存里的,在需要的时候有就行了。

丨极客文库, 版权所有丨如未注明 , 均为原创丨
本网站采用知识共享署名-非商业性使用-相同方式共享 3.0 中国大陆许可协议进行授权
转载请注明原文链接:深入分析 Java 方法反射的实现原理
喜欢 (0)
[247507792@qq.com]
分享 (0)
勤劳的小蚂蚁
关于作者:
温馨提示:本文来源于网络,转载文章皆标明了出处,如果您发现侵权文章,请及时向站长反馈删除。

欢迎 注册账号 登录 发表评论!

  • 精品技术教程
  • 编程资源分享
  • 问答交流社区
  • 极客文库知识库

客服QQ


QQ:2248886839


工作时间:09:00-23:00