深入学习反射原理

1,243 阅读10分钟

介绍

反射是一种通过每个类的java.lang.Class对象动态获取对应的类信息、创建对象以及调用方法的一种机制。

作用

1、在运行时判断任意一个对象所属的类;

2、在运行时构造任意一个类的对象;

3、在运行时判断任意一个类所具有的成员变量和方法;

4、在运行时调用任意一个对象的方法;生成动态代理。

反射的API

Java 类的成员包括以下三类:属性字段、构造函数、方法。反射的 API 也是与这几个成员相关:

  • Field 类:提供有关类的属性信息,以及对它的动态访问权限。它是一个封装反射类的属性的类。
  • Constructor 类:提供有关类的构造方法的信息,以及对它的动态访问权限。它是一个封装反射类的构造方法的类。
  • Method 类:提供关于类的方法的信息,包括抽象方法。它是用来封装反射类方法的一个类。
  • Class 类:表示正在运行的 Java 应用程序中的类的实例。
  • Object 类:Object 是所有 Java 类的父类。所有对象都默认实现了 Object 类的方法。

源码分析


① 例子

import java.lang.reflect.Method;

public class ReflectCase {

    public static void main(String[] args) throws Exception {
        ReflectCase target = new ReflectCase();
        Method method = ReflectCase.class.getDeclaredMethod("test");
        method.invoke(target);
    }

    public void test() {
        System.out.println("test");
    }
}


② getDeclaredMethod方法

//java.lang.Class
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
    throws NoSuchMethodException, SecurityException {
    checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);
    Method method = searchMethods(privateGetDeclaredMethods(false), name, parameterTypes);
    if (method == null) {
        throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
    }
    return method;
}

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


③ searchMethods方法

下面我们进入上面第5行的searchMethods方法(该方法比较简单,不过有个知识点,就是每次都要拷贝一个新的Method对象,也就是说每次都要创建一个Method对象):

private static Method searchMethods(Method[] methods,
                                    String name,
                                    Class<?>[] parameterTypes)
{
    Method res = null;
    String internedName = name.intern();
    //遍历methods数组,找到对应的method对象
    for (int i = 0; i < methods.length; i++) {
        Method m = methods[i];
        if (m.getName() == internedName
            && arrayContentsEq(parameterTypes, m.getParameterTypes())
            && (res == null
                || res.getReturnType().isAssignableFrom(m.getReturnType())))
            res = m;
    }
    //只要传递正确的方法,就会由getReflectionFactory复制一个新的Method对象
    return (res == null ? res : getReflectionFactory().copyMethod(res));
}


④ copy方法:拷贝Method对象

//java.lang.reflect.Method
Method copy() {
    // This routine enables sharing of MethodAccessor objects
    // among Method objects which refer to the same underlying
    // method in the VM. (All of this contortion is only necessary
    // because of the "accessibility" bit in AccessibleObject,
    // which implicitly requires that new java.lang.reflect
    // objects be fabricated for each reflective call on Class
    // objects.)
    if (this.root != null)
        throw new IllegalArgumentException("Can not copy a non-root Method");

    Method res = new Method(clazz, name, parameterTypes, returnType,
                            exceptionTypes, modifiers, slot, signature,
                            annotations, parameterAnnotations, annotationDefault);
    res.root = this;
    // Might as well eagerly propagate this if already present
    res.methodAccessor = methodAccessor;
    return res;
}

从上面的方法中,我们可以看到每次调用getDeclaredMethod方法返回的Method对象其实都是一个新的对象,且新对象的root属性都指向原来的Method对象。因此如果需要频繁调用,最好把Method对象缓存起来。


⑤ privateGetDeclaredMethods方法

下面我们进入上面代码段②中第5行的privateGetDeclaredMethods方法,该方法用于获取缓存或JVM中获取该Class中申明的方法列表。

//java.lang.Class
private Method[] privateGetDeclaredMethods(boolean publicOnly) {
    checkInitted();
    Method[] res;
    ReflectionData<T> rd = reflectionData(); //从jvm或缓存中获取当前类的class对象的信息
    if (rd != null) {
        res = publicOnly ? rd.declaredPublicMethods : rd.declaredMethods;
        if (res != null) return res;
    }
    // No cached value available; request value from VM
    res = Reflection.filterMethods(this, getDeclaredMethods0(publicOnly));
    if (rd != null) {
        if (publicOnly) {
            rd.declaredPublicMethods = res;
        } else {
            rd.declaredMethods = res;
        }
    }
    return res;
}


⑥ reflectionData方法

该方法用来获取ReflectionData对象。

//java.lang.Class
// Lazily create and cache ReflectionData
private ReflectionData<T> reflectionData() {
    SoftReference<ReflectionData<T>> reflectionData = this.reflectionData;
    int classRedefinedCount = this.classRedefinedCount;
    ReflectionData<T> rd;
    if (useCaches &&
        reflectionData != null &&
        (rd = reflectionData.get()) != null &&
        rd.redefinedCount == classRedefinedCount) {
        return rd;
    }
    //如果没有缓存或者说软引用被回收
    //或者该ReflectionData是旧的对象(应该是根据rd.redefinedCount == classRedefinedCount判断的),
    //则会创建新的ReflectionData对象并放入该类的java.lang.Class对象中
    return newReflectionData(reflectionData, classRedefinedCount);
}

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


⑦ ReflectionData类

ReflectionData对象存储了java.lang.Class对象的基本信息。

//java.lang.Class
//该类在用户代码运行前会被调用,从而将对应的类信息缓存起来
// reflection data that might get invalidated when JVM TI RedefineClasses() is called
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;
    }
}


⑧ method.invoke方法

调用method.invoke(target)后会进入该方法。

//java.lang.reflect.Method
public Object invoke(Object obj, Object... args)
    throws IllegalAccessException, IllegalArgumentException,
    InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, obj, modifiers);
            }
        }
        MethodAccessor ma = methodAccessor;             // read volatile
        if (ma == null) {
            ma = acquireMethodAccessor();
        }
        return ma.invoke(obj, args);
    }

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

三个实现类。

image.png


调用过程如下图所示:

⑨ newMethodAccessor方法

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

//sun.reflect.ReflectionFactory
public MethodAccessor newMethodAccessor(Method method) {
    checkInitted();
    if (noInflation && !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) {
        return (new MethodAccessorGenerator())
            .generateMethod(method.getDeclaringClass(), 
                            method.getName(), 
                            method.getParameterTypes(), 
                            method.getReturnType(), 
                            method.getExceptionTypes(), 
                            method.getModifiers());
    } else {
        NativeMethodAccessorImpl acc = new NativeMethodAccessorImpl(method);
        DelegatingMethodAccessorImpl res = new DelegatingMethodAccessorImpl(acc);
        acc.setParent(res);
        return res;
    }
}

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

如果noInflationfalse,方法newMethodAccessor都会返回DelegatingMethodAccessorImpl对象;

反之,如果noInflationtrue,比如在参数中设置-Dsun.reflect.noInflation=true,则每次都使用GeneratedMethodAccessorImpl对象(这个对象是在内存里新生成的专用Java类,它是Java版的MethodAccessor的实现类)。


⑩ NativeMethodAccessorImpl类

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

//sun.reflect.NativeMethodAccessorImpl
class NativeMethodAccessorImpl extends MethodAccessorImpl {
    private  Method method;
    private DelegatingMethodAccessorImpl parent;
    private int numInvocations;

    NativeMethodAccessorImpl(Method method) {
        this.method = method;
    }

    public Object invoke(Object obj, Object[] args) 
        throws IllegalArgumentException, InvocationTargetException 
    {
        if (++numInvocations > ReflectionFactory.inflationThreshold() 
            && !ReflectUtil.isVMAnonymousClass(this.method.getDeclaringClass())) {
            MethodAccessorImpl acc = (MethodAccessorImpl)
                (new MethodAccessorGenerator())
                .generateMethod(this.method.getDeclaringClass(), 
                                this.method.getName(),
                                this.method.getParameterTypes(),
                                this.method.getReturnType(),
                                this.method.getExceptionTypes(),
                                this.method.getModifiers());
            this.parent.setDelegate(acc);
        }

        return invoke0(this.method, obj, args);
    }

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

也就是说,每次NativeMethodAccessorImpl.invoke()方法被调用时,都会增加一个调用次数计数器,看超过阈值没有;一旦超过,则调用MethodAccessorGenerator.generateMethod()来生成Java版的MethodAccessor的实现类,并且改变DelegatingMethodAccessorImpl所引用的MethodAccessor为Java版。后续经由DelegatingMethodAccessorImpl.invoke()调用到的就是Java版的实现了。


⑪ defineClass方法

generateMethod方法在生成MethodAccessorImpl对象时,会在内存中生成对应的字节码,并调用ClassDefiner.defineClass创建对应的class对象。

//sun.reflect.ClassDefiner
static Class<?> defineClass(String name, byte[] bytes, int off, int len, 
                            final ClassLoader parentClassLoader) {
    ClassLoader newLoader = (ClassLoader)AccessController.doPrivileged(
        new PrivilegedAction<ClassLoader>() {
        public ClassLoader run() {
            return new DelegatingClassLoader(parentClassLoader);
        }
    });
    return unsafe.defineClass(name, bytes, off, len, newLoader, null);
}

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


⑫ GeneratedMethodAccessor生成过程代码

该方法用来在内存里生成新的专用Java类的名称。在invoke调用超过15次时使用的MethodAccessor的名字就是下面的这个GeneratedMethodAccessor。

private static synchronized String generateName(boolean isConstructor,
                                                boolean forSerialization)
{
    if (isConstructor) {
        if (forSerialization) {
            int num = ++serializationConstructorSymnum;
            return "sun/reflect/GeneratedSerializationConstructorAccessor" + num;
        } else {
            int num = ++constructorSymnum;
            return "sun/reflect/GeneratedConstructorAccessor" + num;
        }
    } else {
        int num = ++methodSymnum;
        return "sun/reflect/GeneratedMethodAccessor" + num;
    }
}


image.png


问题

① 为什么要有两个MethodAccessor实现版本?

答:实际的 MethodAccessor 实现有两个版本,一个是 Native 版本NativeMethodAccessorImpl,一个是 Java 版本MethodAccessorImpl。Java版本在第一次启动时,需要加载字节码实现Method.invoke() 和Constructor.newInstance() ,比较耗时。所以Native 版本第一次启动比java版本快3-4倍,但是后面的调用java版本比Native快20倍。所以为了避免启动慢,第一次使用native版本快速启动。因此为了避免后续运行慢,会在调用了15次invoke方法之后再切换到java版本MethodAccessorImpl 对象去实现反射。

image.png


总结

  1. 反射的开销随着版本的升级不断降低,但还是比直接调用的开销要大。

  当该反射调用成为热点时,它甚至可以被内联到靠近Method.invoke()的一侧,从而大大降低了反射调用的开销。而native版的反射调用则无法被有效内联,因而调用开销无法随程序的运行而降低。

  虽说Sun的JDK这种实现方式使得反射调用方法成本比以前降低了很多,但Method.invoke()本身要用数组包装参数;而且每次调用都必须检查方法的可见性(在Method.invoke()里),也必须检查每个实际参数与形式参数的类型匹配性(在NativeMethodAccessorImpl.invoke0()里或者生成的Java版本的MethodAccessor.invoke()里);而且Method.invoke()就像是个独木桥一样,各处的反射调用都要挤过去,在调用点上收集到的类型信息就会很乱,影响内联程序的判断,使得Method.invoke()自身难以被内联到调用方。

  相比之下JDK7里新的MethodHandler则更有潜力,在其功能完全实现后能达到比普通反射调用方法更高的性能。在使用MethodHandle来做反射调用时,MethodHandle.invoke()的形式参数与返回值类型都是准确的,所以只需要在链接方法的时候才需要检查类型的匹配性,而不必在每次调用时都检查。而且MethodHandle是不可变值,在创建后其内部状态就不会再改变了;JVM可以利用这个知识而放心的对它做激进优化,例如将实际的调用目标内联到做反射调用的一侧。

  1. 每次使用反射来调用方法时,都会重新创建Method对象,因此,建议对Method对象进行缓存。
  2. 反射的底层通过ReflectionData对象对每个类的基本信息进行缓存。ReflectionData对象被软引用所关联,因此当要gc时,该对象会被回收。
  3. 反射中通过生成新的ClassLoader来及时卸载那些不希望一直存储在内存里的类。
  4. MethodAccessor(可以理解为Method调用器)实现有两个版本,一个是 Native 版本NativeMethodAccessorImpl,一个是 Java 版本MethodAccessorImpl。Java版本在第一次启动时,需要加载字节码实现Method.invoke() 和Constructor.newInstance() ,比较耗时。所以Native 版本第一次启动比java版本快3-4倍,但是后面的调用java版本比Native快20倍。所以为了避免启动慢,第一次使用native版本快速启动。因此为了避免后续运行慢,会在调用了15次invoke方法之后再切换到java版本MethodAccessorImpl 对象去实现反射。
  5. 个人感觉反射的原理不难,无非就是通过java.lang.Class对象动态获取类信息,然后去调用类中的方法;我认为在源码中比较复杂、或者说篇幅比较大的部分在于对反射操作的优化,比如通过ReflectionData对类信息进行缓存,而不用去方法区获取;当多次调用native方法时,会生成Java版的方法,这样可以避免多次调用native方法,从而提高调用效率。

参考

  1. 深入分析Java方法反射的实现原理 -- 占小狼
  2. JAVA深入研究——Method的Invoke方法