深入jvm 06. 实现反射

1、反射 API

1)获取Class对象:①使用静态方法Class.forName(“类名”);②调用对象的getClass()方法;③使用类名.class获取。对于基本数据类型,使用 类型名.class,比如 int.class。对于数组类型,使用 类名[].class获取,比如 int[].class。

2)几项常用的反射功能
①对Class对象调用 getFields(),表示获取该类及其父类中声明为public的字段;而调用 getDeclaredFields(),表示获取该类中声明的所有字段(不包含父类中声明的字段)。

② getMethods() / getDeclaredMethods(),getConstructors() / getDeclaredConstructors(),与①中类似,方法名带Declared的不返回父类的成员,但返回自身所有成员(包括私有);而不带Declared的则会返回父类成员,但只返回公有成员。

③调用Class对象的getDeclaredConstructor(构造器各形参的Class对象列表),生成该类的一个构造器,再对该构造器调用newInstance(实参列表)来生成该类的一个实例。

④调用Class对象的 isInstance(Object) 来判断这个对象是否是该类的实例。

⑤对Constructor/Field/Method对象调用setAccessible(true),来获取访问权限。

⑥对Field对象调用 get/set(Object) 来读取/设置字段值。

⑦对Method对象调用 invoke(Object, Object[]),表示对Object对象调用该对象的Method方法,参数列表为Object[]。

2、方法的反射调用

public Object invoke(Object obj, Object... args)
    throws IllegalAccessException, IllegalArgumentException,
       InvocationTargetException
{
    
    	//权限验证
    if (!override) {
    
    
        Class<?> caller = Reflection.getCallerClass();
        checkAccess(caller, clazz,
                    Modifier.isStatic(modifiers) ? null : obj.getClass(),
                    modifiers);
    }
    MethodAccessor ma = methodAccessor;             // read volatile
    if (ma == null) {
    
    
        ma = acquireMethodAccessor();
    }
    return ma.invoke(obj, args);
}

从 jdk 源码可以发现,Method的中的invoke方法,实际上是由MethodAccessor类型的对象来处理,MethodAccessor是一个接口,这个接口有两个具体实现类,一个是使用了委派模式来实现,另一个是通过本地方法(由C++实现)来实现(本地实现)。

每个Method实例的第一次方法反射调用会先进入委派实现类 DelegatingMethodAccessorImpl,再进入本地实现类 NativeMethodAccessorImpl,最后到达目标方法

在方法反射调用超过15次后,委派实现不会再使用本地实现,转而使用动态实现,这是一个动态生成字节码的实现,直接使用 invoke 指令来调用目标方法。

动态实现和本地实现相比,运行效率要快20倍,动态实现不需要经过 java 到 C++ 再到 java 的切换,但是多了生成字节码的时间。因此,对于很多仅调用1次的反射来说,如果使用动态实现,速度反而会比本地实现慢3到4倍。而反射调用一般只会执行1次,所以 java 虚拟机设置了15为阈值,方法反射调用15次以内,使用本地实现,超过15次则开始动态生成字节码,并使用动态实现。使用委派实现,是为了能够在本地实现和动态实现中切换。

3、反射调用的性能开销

1)对于热点代码(如循环):以 getMethod 为代表的查找方法操作,会返回查找得到结果的一份拷贝,如果在循环中重复拷贝对象,会加大堆空间的消耗,所以不应该在循环内使用这类方法,而是应该在循环外缓存这类方法的结果。此外,建议在循环外部就对目标方法的权限进行设置,以免每轮循环都要重新检查方法权限。并且关闭掉委派实现( -Dsun.reflect.noInflation=true),直接使用动态实现,因为动态实现运行效率更快。

2)Method.invoke()方法的变长参数会导致生成Object数组,用于存放所有的参数,如果遇到基本数据类型,还需要进行装箱、拆箱

猜你喜欢

转载自blog.csdn.net/Longstar_L/article/details/107631724