反射分析一

作者: space0o0 | 来源:发表于2021-01-20 14:18 被阅读0次

获取函数
使用伪代码来简化源码的复杂逻辑,只关注主线逻辑。

Java中万物都是对象,如果使用过反射的同学,应该有所感受。Class是最基本的对象,而其中的函数(method)、属性(field)等,也可以看作是一个对象。不然,method不是个东西,我们怎么调用呢?

首先看下反射的使用

// 创建对象
Object innerClazz = InnerClazz.class.newInstance();
// 获取函数
Method doTaskMethod = InnerClazz.class.getDeclaredMethod("doTask");
// 调用函数
doTaskMethod.invoke(innerClazz);

获取所有函数信息

Class对象内部有一个函数:getDeclaredMethod,传入函数名和参数后,可以得到对应的Method对象。该函数的实现伪代码如下。

// *** 伪代码 ***
void getDeclaredMethod(String methodName , Class<?> params){
    // 从class中获取所有的函数信息
    Method[] declaredMethods = privateGetDeclaredMethods();
    // 根据methodName 和 参数,查找method
    Method method = searchMethod(declaredMethods, methodName, params);
}

首先,要获取Method对象,那就从该Class对象中寻找。privateGetDeclaredMethods()就是获取该Class对象的所有函数信息(返回的是Method数组)。接着,根据函数名(methodName)和函数参数(params)找到对应的Method对象。

// *** privateGetDeclaredMethods() 伪代码 ***
Method[] privateGetDeclaredMethods(){
    // 可以看成Class的缓存信息,里面有函数信息、属性信息等
    ReflectionData<T> rd = reflectionData();
    // 从缓存中获取 函数列表
    Method[] res = rd.declaredMethods;
    if (res == null){
        // res为空 向jvm请求获取res
        res = JVM.getMethods();
        // 缓存到rd中
        rd.declaredMethods = res;
    }
    return res;
}

为了提高性能,所以需要对所有 函数 进行一个缓存,缓存的对象就是ReflectionData。当发现缓存没有,就可以使用native函数向JVM请求获取函数信息,同时保存到缓存中。

查找对应的函数

经过上面的步骤后,缓存中保存了Class的函数信息,同时也返回了所有函数信息,现在就可以根据函数名 和 函数参数 对比查找需要反射的Method对象了。

Method searchMethods(Method[] methods, String methodName, Class<?> params){
    Method res = null;
    // for循环挨个比较
    for(int i = 0; i<methods.length; i++){
        if(满足条件){
            res = methods[i]
        }
    }
    return res == null ? res.copy();
}

搜索函数很简单,就是函数名和参数的比较,如果没找到,就会在外部收到null,抛出NoSuchMethodException异常。
如果找到了,就复制一个该函数对象。相当于从原始的函数信息中复制一份出来。

函数的调用(invoke)

在获取到了Method对象后,我们进行第二步:函数的调用。

// Method的invoke函数 伪代码
Object invoke(Object obj, Object... args){
    MethodAccessor ma = methodAccessor;
    if (ma == null){
        // 创建一个MethodAccessor
        ma = acquireMethodAccessor();
    }
    return ma.invoke(obj,args);
}

Method 的 invoke函数内部调用了MethodAccessor的invoke函数。

MethodAccessor是个接口,实现类有3个,其中1个是作为代理类,另外2个才是真正干活的。

代理类:DelegatingMethodAccessorImpl
干活类:NativeMethodAccessorImpl 和 MagicAccessorImpl

MethodAccessor调用invoke后,进入DelegatingMethodAccessorImpl的invoke函数,其中再调用实际的干活类。

// NativeMethodAccessorImpl的invoke
Object invoke(){
    if(调用次数 > 15){
        为DelegatingMethodAccessorImpl 设置 MagicAccessorImpl作为实际干 活类。
    }
    调用 native 的invoke
}

NativeMethodAccessorImpl的invoke函数中,会记录该函数的调用次数,如果大于15次,就设置代理类的干活类为MagicAccessorImpl
从类名中可以看出:NativeMethodAccessorImpl是native的调用者,MagicAccessorImpl相对的是Java层面的invoke调用者。

native函数底层是让JVM调用JVM_InvokeMethod()函数
Java层的调用可以从MethodAccessorGenerator源码中看出,实际是在内存中生成函数的字节码,让JVM直接调用。

其中,这里最绕的就是调用次数达到了15次后,使用Java生成字节码来实现函数的调用。

为什么大于15次需要更换delegate?
参考其他文章的结论:

动态实现(Java)和本地实现(native)相比,执行速度要快上20倍,这是因为动态实现直接执行字节码,不用从java到c++ 再到java 的转换,但是因为生成字节码的操作比较耗费时间,所以如果仅一次调用的话反而是本地时间快3到4倍。

结尾

简单从Java源码层面分析了反射的执行过程,其中还有许多细节需要研究。

包括两个实现类为何执行速度相差这么大?
native层面,是如何实现invoke的?
Android中的反射涉及到hidden API,是如何工作的?

参考

java 反射原理(jvm是如何实现反射的) - 简书
JAVA深入研究——Method的Invoke方法。 - 寂静沙滩 - 博客园
假笨说-从一起GC血案谈到反射原理

相关文章

  • 反射分析一

    获取函数使用伪代码来简化源码的复杂逻辑,只关注主线逻辑。 Java中万物都是对象,如果使用过反射的同学,应该有所感...

  • 反射

    我的博客 关于反射的定义和用途: 反射的定义 能够分析类能力的程序 反射的用途: 运行中分析类的能力 运行中查看对...

  • Java反射(一)之Class类

    一、反射 在开始之前,有必要先简单的介绍一下Java的反射(reflective)。能够分析类能力的程序称为反射,...

  • 反射---Class类

     能够分析类能力的程序称为反射。反射机制的功能极其强大,反射机制可以用来: 在运行时分析类的能力 在运行时查看对象...

  • Kotlin反射(1)反射API

      反射(Reflection) 是程序的自我分析能力,通过反射可以确定类中有哪些函数以及属性。反射机制在一般的应...

  • java反射

    反射: 概念:能够分析类能力的程序称为反射。反射机制的功能极其强大,在下面可以看到,反射机制可以用来: 在运行时分...

  • Java反射机制干货总结(一)

    一、初步认识反射### 使用反射,能够快速地应用开发工具动态地查询新添加类的能力。能够分析类能力的程序称为反射。反...

  • JAVA语言反射机制

    一、反射的定义 反射:能够分析类能力的程序叫做反射。换一种说法就是,一种动态获取类的信息以及动态调用对象的方法的功...

  • ART的反射调用(二)--创建对象实例

    通过反射创建实例的用法是: 通过Class.forName在上一篇ART的反射调用(一)中已经分析 一. Clas...

  • Java基础知识系列—Java反射

    能够分析类能力的程序称为反射(reflective)。反射机制的功能非常强大,主要提供了如下功能: 对于任意一个类...

网友评论

    本文标题:反射分析一

    本文链接:https://www.haomeiwen.com/subject/kgdkzktx.html