Java中反射源码解析

Java中反射源码解析

一、反射原理阐述

1、从应用层角度

在JVM类型的编程语言中,任何一个编译好的类都会生成class文件, 在被类加载器加载完后都会有一个java.lang.Class<T>这个类Class实例对应。所以每个类的一些方法和属性(或类结构)自然会被包含在对应的实例上,因此都可以获取到。

2、从JVM层角度

Class文件一组以8位字节为基础单位的二进制流,各个数据项目按严格的顺序紧凑的排列在Class文件中。文件中记录以下信息,比如版本号、常量池、字面量、符号引用、访问标志、类索引等非常全面的类信息,有这个这些信息就可以轻松获取到。

二、Java中方法Method反射源码解析

1、Method对象查找和获取
  • 1、跳入getDeclaredMethod函数定义

        //Class类中的getDeclaredMethod方法
        @CallerSensitive
        public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
            throws NoSuchMethodException, SecurityException {
            checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);
            //调用searchMethods方法将从privateGetDeclaredMethods方法返回Method列表中找到与之name,参数类型匹配的Method对象
            Method method = searchMethods(privateGetDeclaredMethods(false), name, parameterTypes);
            if (method == null) {
                throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
            }
            //最后将找到Method对象返回即可
            return method;
        }
    
  • 2、然后接着进入privateGetDeclaredMethods函数

        private Method[] privateGetDeclaredMethods(boolean publicOnly) {
            checkInitted();
            Method[] res;
    
            ReflectionData<T> rd = reflectionData();
            if (rd != null) {
            //先从ReflectionData缓存中获取Method列表,如果之前有过反射操作,缓存中就会存在Method列表
                res = publicOnly ? rd.declaredPublicMethods : rd.declaredMethods;
                //如果缓存中存在res不为null,直接返回res
                if (res != null) return res;
            }
            // 如果缓存为空,就直接从JVM中去请求获取Method列表,getDeclaredMethods0是个Native方法,就是直接从JVM中获取所有的Method列表,然后通过filterMethods筛选出当前反射类的Method列表
            res = Reflection.filterMethods(this, getDeclaredMethods0(publicOnly));
            if (rd != null) {
            //将请求会的Method列表,存入ReflectionData缓存中,以备下次使用
                if (publicOnly) {
                    rd.declaredPublicMethods = res;
                } else {
                    rd.declaredMethods = res;
                }
            }
            //最后返回Method列表
            return res;
        }
  • 3、再看reflectionData()方法,如果没有被GC回收,那么就直接从缓存去取,如果被GC回收掉了,通过newReflectionData()方法创建并替换新的reflectionData实例

       //懒加载和缓存ReflectionData
       private ReflectionData<T> reflectionData() {
       //使用了软引用
            SoftReference<ReflectionData<T>> reflectionData = this.reflectionData;
            int classRedefinedCount = this.classRedefinedCount;
            ReflectionData<T> rd;
            //如果useCaches为true且reflectionData没有被GC回收,那么就直接从reflectionData中get方法获取,然后就直接返回。
            if (useCaches &&
                reflectionData != null &&
                (rd = reflectionData.get()) != null &&
                rd.redefinedCount == classRedefinedCount) {
                return rd;
            }
          //由于reflectionData是软引用类型,只要发生GC就会将其回收,如果reflectionData被回收之后需要调用newReflectionData来创建和替换芯的ReflectionData实例对象
            return newReflectionData(reflectionData, classRedefinedCount);
        }
  • 4、ReflectionData数据结构

     //ReflectionData数据结构,内部存储了成员变量Field的对象数组,成员方法Method对象数组以及构造器Constructor对象数组
     private static class ReflectionData<T> {
            volatile Field[] declaredFields;
            volatile Field[] publicFields;
            volatile Method[] declaredMethods;
            volatile Method[] publicMethods;
            volatile Constructor<T>[] declaredConstructors;
            volatile Constructor<T>[] publicConstructors;
            // Intermediate results for getFields and getMethods
            volatile Field[] declaredPublicFields;
            volatile Method[] declaredPublicMethods;
            volatile Class<?>[] interfaces;
    
            // Value of classRedefinedCount when we created this ReflectionData instance
            final int redefinedCount;
    
            ReflectionData(int redefinedCount) {
                this.redefinedCount = redefinedCount;
            }
        }
  • 5、接着研究newReflectionData()方法

  • 6、Method对象查找获取流程)

  • 7、然后接着进入searchMethods方法)

  • 8、然后进入ReflectionFactor中的copyMethod方法)

  • 9、然后进入LangReflectAccess接口的实现类ReflectAccess中的copyMethod方法)

  • 10、copyMethod实际上最后会调用Method的copy函数,返回Method的一个副本)

2、Method对象的invoke调用
  • 1、获取到指定的Method的对象后就可以调用它的invoke方法了)

  • 2、接着跳入到acquireMethodAccessor方法)

  • 3、创建新的MethodAccessor实例是委托给ReflectionFactory中的newMethodAccessor方法中,MethodAccessor就是上面提到的所有同名method共享的一个实例,由ReflectionFactory创建。创建机制采用了一种名为inflation的方式(JDK1.4之后):如果该方法的累计调用次数<=15,会创建出NativeMethodAccessorImpl,它的实现就是直接调用native方法实现反射;如果该方法的累计调用次数>15,会由java代码创建出字节码组装而成的MethodAccessorImpl。(是否采用inflation和15这个数字都可以在jvm参数中调整))

  • 4、检查是否初始化checkInitted方法)

  • 5、NativeMethodAccessorImpl的实现)

3、反射性能检测

public class ReflectCase {

    public static void main(String[] args) {
        Proxy proxy = new Proxy();
        for (int i = 0; i < 20; i++) {
            reflectPrintRun(proxy, i);
        }
    }

    static void reflectPrintRun(Proxy proxy, int index) {
        long startTime = System.nanoTime();
        try {
            Method method = Proxy.class.getDeclaredMethod("printRun");
            method.invoke(proxy);
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }

        System.out.println("第" + (index + 1) + "次反射, took time is " + (System.nanoTime() - startTime) + " ns");
    }

    static class Proxy {
        public void printRun() {
            System.out.println("this is run method");
        }
    }
}

输出结果:

性能曲线:

结论:

实际的MethodAccessor实现有两个版本,一个是Java实现的,另一个是native code实现的。Java实现的版本在初始化时需要较多时间,但长久来说性能较好;native版本正好相反,启动时相对较快,但运行时间长了之后速度就比不过Java版了。这是HotSpot的优化方式带来的性能特性,同时也是许多虚拟机的共同点:随着调用次数的增加,每次反射都使用JNI跨越native边界会对优化有阻碍作用,它就像个黑箱一样让虚拟机难以分析也将其内联,于是运行时间长了之后反而是托管版本的代码更快些。
为了权衡两个版本的性能,Sun的JDK使用了“inflation”的技巧:让Java方法在被反射调用时,开头若干次使用native版,等反射调用次数超过阈值时则生成一个专用的MethodAccessor实现类,生成其中的invoke()方法的字节码,以后对该Java方法的反射调用就会使用Java版。


   转载规则


《Java中反射源码解析》 mikyou 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
Android中线程Thread源码解析 Android中线程Thread源码解析
Android中线程Thread源码解析一、Thread的介绍 Thread线程是进程中的独立运行的子任务。线程是CPU调度的最基本的单元,线程资源开销相对于进程而言比较小,所以我们一般是创建线程来执行任务。 1、Thread继承关系 T
2019-12-29
下一篇 
Android线程池ThreadPoolExecutor源码解析 Android线程池ThreadPoolExecutor源码解析
Android线程池ThreadPoolExecutor源码解析一、ThreadPoolExecutor介绍1、定义 ThreadPoolExecutor线程池本质就是缓存一定线程数量的区域(池子) 2、作用 实现对批量线程的统一管理、
2019-12-28