Java反射原理简析

Java的反射机制允许我们动态的调用某个对象的方法/构造函数,获取某个对象的属性等,而无需在编码时确定调用的对象。这种机制在我们常用的框架中也非常常见。

1.原理简介

类actionClass = Class.forName(“ MyClass”);
对象action = actionClass.newInstance();
方法method = actionClass.getMethod(“ myMethod”,null);
method.invoke(action,null);

上面就是最常见的反射使用的例子,前两行实现了类的装载,链接和初始化(newInstance方法实际上也是使用反射调用了方法),后两行实现了从类对象中获取到方法对象然后执行反射调用。下面简单分析一下后两行的原理。

其实我们预言,上面的代码中,如果想要实现method.invoke(action,null)调用action对象的myMethod方法,只需要实现这样一个方法类即可:

类方法{

     公共对象invoke(Object obj,Object [] param){

        MyClass myClass =(MyClass)obj;

        返回myClass.myMethod();

     }

}

反射的原理之一其实就是动态的生成上述的字节码,加载到jvm中运行。

2.获取方法对象

首先来看一下方法对象是如何生成的:

Java反射原理简析_第1张图片

上面的类对象是在加载类时由JVM结构的,JVM为每个类管理一个独一无二的类对象,此类对象里维护着该该类的所有方法,字段,构造函数的缓存,这份缓存也可以被get根对象。每次getMethod获取到的方法对象都持有对根对象的引用,因为一些重量级的Method的成员变量(主要是MethodAccessor),我们不希望每次创建方法对象都要重新初始化,于是所有代表同一个方法的方法对象都共享着根对象的MethodAccessor,每一次创建都会调用根对象的副本方法复制一份:

方法copy(){ 

        方法res =新方法(clazz,名称,parameterTypes,returnType,

                                exceptionTypes,修饰符,广告位,签名,

                                注释,parameterAnnotations,annotationDefault);

        res.root =这个;

        res.methodAccessor = methodAccessor;

        返回资源;

    }

3.调用invoke()方法

获取到方法对象之后,调用invoke方法的流程如下:

Java反射原理简析_第2张图片
可以看到,调用Method.invoke之后,会直接去调MethodAccessor.invoke。MethodAccessor就是上面提到的所有同名方法共享的一个实例,由ReflectionFactory创建。 4之后):如果该方法的累积调用次数<= 15,会创建出NativeMethodAccessorImpl,它的实现就是直接调用nativeMethod实现反射;如果该方法的累计调用次数> 15,会由java代码创建出字节码(是否采用inflation和15个数字都可以在jvm参数中调整)
以调用MyClass.myMethod(String s)为例,生成出的MethodAccessorImpl字节码翻译成Java代码大致如下:

公共类GeneratedMethodAccessor1扩展了MethodAccessorImpl {    
    公共对象invoke(Object obj,Object [] args)引发异常{
        尝试{
            MyClass目标=(MyClass)obj;
            字符串arg0 =(String)args [0];
            target.myMethod(arg0);
        } catch(Throwable t){
            抛出新的InvocationTargetException(t);
        }
    }
}

这样的native方法的实现,由于比较深入此处就不探讨了,欢迎有兴趣的同学来补充。

4.性能

下面分析一下反射的性能相关的问题,以及为什么要有通货膨胀这个机制。下图是我本地循环使用反射二十次的耗时(单位ns):
Java反射原理简析_第3张图片
从变化趋势上看,第1次和第16次调用是最耗时的(初始化NativeMethodAccessorImpl和字节码拼装MethodAccessorImpl)。然后初始化是替代的,而native方式的初始化会替代,因此前一次的调用会采用native方法。
随着调用次数的增加,每次反射都使用JNI跨越nativenative边界变量优化有干扰作用,相对来说使用拼写装出的字节码可以直接以Java调用的形式实现反射,发挥了JIT优化的作用,避免了JNI为了维护OopMap(HotSpot实现实现准确式GC的数据结构)进行封装/解压缩封装的性能提高。因此在已经创建了MethodAccessor的情况下,使用Java版本的实现会因此当调用次数到达一定次数(15次)后,会切换成Java实现的版本,来优化未来可能的更交替的反射调用。

以上就是本次对反射的分享学习,推荐文章R大的博文http://rednaxelafx.iteye.com/blog/548536,和代码相关的细节在介绍文章里写得比较多,笔者也是在这篇文章基础上进行学习探究的。

你可能感兴趣的:(java反射原理)