java反射原理解析

在探究反射原理之前,有必要来了解一下Class对象,反射的关键就是Class对象。

Class对象

Class对象是每个类运行时的类型信息。

JVM在加载class时,会创建instanceKlass,表示其元数据,包括常量池,字段,方法等,存放在方法区,instanceKlass是JVM中的数据结构;
在new一个对象时,JVM创建instanceOopDesc来表示这个对象,存放在堆中,其引用存放在栈中;instanceOopDesc对应Java中的对象实例;
HotSpot并不会把instanceKlass暴露给Java开发者,而会另外创建对应的instanceOopDesc来表示java.lang.Class对象,并将后者称为前者的镜像。
new操作返回的instanceOopDesc类型指针指向instanceKlass,而instanceKlass指向对应类型的Class实例的instanceOopDesc
在JDK6中,Class对象存放在方法区,JDK7和JDK8中,Class对象存放在Java堆中。

Class类没有公共的构造方法,Class对象是在类加载的时候由Java虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。

Class对象在类加载的加载阶段生成,加载阶段主要包括下面三个步骤:

  • 由类加载器(ClassLoader)执行的。通过类的全限定类名,获取此文件的二进程流,就是加载.class文件(class文件中存储的就是类编译后的二进制数据)
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  • 在内存中生成该类的Class对象,作为方法区访问该类各种数据的访问接口

获取Class对象的三种方式

  • Class.forName(“全限定类名”)
  • 实例对象.getClass()
  • 类名.class

反射

反射是在JVM运行的时候通过Class对象动态的获取类信息。方法main也是通过发射来运行的。

Class类中无论什么 getXXX() 方法(XXX表示成员变量,成员方法或者构造方法,如getDeclaredMethod方法),内部都调用了privateGetXXX这个方法,该方法内部的前三行又都调用了下面这行代码。

ReflectionData<T> rd = reflectionData();

rd是个缓存对象,缓存的内容实际上是反射需要获取的成员变量、成员方法、构造方法、类接口等可以由每个线程获取到的变量。这样做的原因在于提高效率,多线程同时读取时就不用都去底层获取这些变量了,ReflectionData的源码如下

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;
        final int redefinedCount;
        ReflectionData(int redefinedCount) {
            this.redefinedCount = redefinedCount;
        }
    }

Class用软引用持有了一个缓存对象,在JVM内存吃紧的情况下防止OOM会把这个缓存对象回收,所以其失效时间是内存不足的时候。

private volatile transient SoftReference<ReflectionData<T>> reflectionData;

另外,因为ReflectionData中的变量都修饰了volatile或者final,所以都是内存可见的,结合CAS操作可以实现线程安全。
然后,来分析reflectionData() 这个方法,假设现在多个线程同时调用了这个方法,那么在Class内部已经持有该缓存对象的情况下不会出现线程安全问题(因为都是读操作),但是在Class对象内部没持有该对象的时候,多个线程同时创建这个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;
        }
        return newReflectionData(reflectionData, classRedefinedCount);
    }

private ReflectionData<T> newReflectionData(SoftReference<ReflectionData<T>> oldReflectionData,
                                                int classRedefinedCount) {
        if (!useCaches) return null;
        while (true) {
            ReflectionData<T> rd = new ReflectionData<>(classRedefinedCount);
            if (Atomic.casReflectionData(this, oldReflectionData, new SoftReference<>(rd))) {
                return rd;
            }
            oldReflectionData = this.reflectionData;
            classRedefinedCount = this.classRedefinedCount;
            if (oldReflectionData != null &&
                (rd = oldReflectionData.get()) != null &&
                rd.redefinedCount == classRedefinedCount) {
                return rd;
            }
        }
    }

当缓存对象不存在的时候会调用newReflectionData,注意到该方法实际上用了一个死循环+CAS操作,由于reflectionData变量是volatile内存可见的,Atomic.casReflectionData保证线程安全的创建了缓存对象,CAS不成功的线程会循环创建,并CAS覆盖之前的缓存。
成员变量,成员方法和构造方法的获取时间,以Method成员方法为例,会在在某个线程调用getDeclaredMethod方法获取/创建了缓存对象之后,接着首先会从缓存中获取该方法,如果获取不到才会调用Reflection.filterMethods方法获取Method数组,然后更新到缓存中。

private Method[] privateGetDeclaredMethods(boolean publicOnly) {
        checkInitted();
        Method[] res;
        ReflectionData<T> rd = reflectionData();
        if (rd != null) {
            res = publicOnly ? rd.declaredPublicMethods : rd.declaredMethods;
            if (res != null) return res;
        }
        // 没有缓存的条件下从虚拟机中获取
        res = Reflection.filterMethods(this, getDeclaredMethods0(publicOnly));
        if (rd != null) {
            if (publicOnly) {
                rd.declaredPublicMethods = res;
            } else {
                rd.declaredMethods = res;
            }
        }
        return res;
    }

上面获取到的是一个数组,而要想获取到指定名称的那个还经历了一个searchMethods方法。方法代码如下,该方法在匹配到了指定的Method之后会通过getReflectionFactory().copyMethod(res)底层调用copy方法,该方法返回的是一个新的Method对象,且对象持有一个根方法对象。所以如果是多线程调用这个方法实际上是每个线程都new了一个对象,这样做的好处类似于Spring中的原型,不会出现线程之间同时更改同一个Method对象的线程安全问题。FieldConstructor的方法原理都是相同的。

private static Method searchMethods(Method[] methods,
                                        String name,
                                        Class<?>[] parameterTypes)
    {
        Method res = null;
        String internedName = name.intern();
        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;
        }

        return (res == null ? res : getReflectionFactory().copyMethod(res));
    }
Method copy() {
        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;
    }

以上反射获取对应对象过程结束,下面是对对象的使用,这里同样以方法为例,获取到方法之后会调用invoke方法执行方法。方法代码如下,该方法首先进行了一系列的检测,然后通过MethodAccessor调用invoke方法执行Method,开始时MethodAccessor为空,需要获取。


 @CallerSensitive
    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方法,首先也是从缓存中获取,如果获取不到就调用reflectionFactory.newMethodAccessor方法获取一个并设置缓存

private MethodAccessor acquireMethodAccessor() {
        MethodAccessor tmp = null;
        if (root != null) tmp = root.getMethodAccessor();
        if (tmp != null) {
            methodAccessor = tmp;
        } else {
            tmp = reflectionFactory.newMethodAccessor(this);
            setMethodAccessor(tmp);
        }
        return tmp;
    }

然后,具体看下newMethodAccesor方法,该方法可以使用inflation机制创建accessor,初始的时候noInfalation值为false,所以开始的时候会调用NativeMethodAccessorImpl的方法invoke

private static boolean noInflation        = false;
private static int     inflationThreshold = 15;
 public MethodAccessor newMethodAccessor(Method method) {
        checkInitted();
        if (noInflation) {
            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;
        }
    }

再次,看下NativeMethodAccessorImpl的实现,这里的++numInvocations就是统计次数的,有源码可以看出,前15次会调用native的invoke0方法,这种方法初始化比较快,但性能不够好。而过了15次之后都是底层调用asm给Method生成字节码然后加载到内存形成对象进行调用的,这种方式初始化较慢,但性能较好。

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()) {
            MethodAccessorImpl acc = (MethodAccessorImpl)
                new MethodAccessorGenerator().
                    generateMethod(method.getDeclaringClass(),
                                   method.getName(),
                                   method.getParameterTypes(),
                                   method.getReturnType(),
                                   method.getExceptionTypes(),
                                   method.getModifiers());
            parent.setDelegate(acc);
        }
        return invoke0(method, obj, args);
    }
    void setParent(DelegatingMethodAccessorImpl parent) {
        this.parent = parent;
    }
    private static native Object invoke0(Method m, Object obj, Object[] args);
}

最后,来看下asm生成字节码方法的实现,这个方法的最后返回了一个MagicAccesorImpl(开始是没有实现的空类),其中的AccessController.doPrivileged(new PrivilegeAction)相当于执行一个任务,该任务调用ClassDefinerdefineClass方法,该方法内部同样也是执行任务,但内部的方法每次执行的时候都会返回一个类加DelegatingClassLoader类加载器,然后把类的一些字节码信息连同这个类加载器一起交给unsafe让他去把字节码通过类加载器加载到内存里面来以MagicAccessorImpl的类形式存在与内存中。

return (MagicAccessorImpl) AccessController.doPrivileged(new PrivilegedAction() {
                public MagicAccessorImpl run() {
                    try {
                        return (MagicAccessorImpl) ClassDefiner
                                .defineClass(arg12, arg16, 0, arg16.length, arg0.getClassLoader()).newInstance();
                    } catch (IllegalAccessException | InstantiationException arg1) {
                        throw new InternalError(arg1);
                    }
                }
            });
class MagicAccessorImpl {
}
class ClassDefiner {
    static final Unsafe unsafe = Unsafe.getUnsafe();
    static Class<?> defineClass(String arg, byte[] arg0, int arg1, int arg2, final ClassLoader arg3) {
        ClassLoader arg4 = (ClassLoader) AccessController.doPrivileged(new PrivilegedAction() {
            public ClassLoader run() {
                return new DelegatingClassLoader(arg3);
            }
        });
        return unsafe.defineClass(arg, arg0, arg1, arg2, arg4, (ProtectionDomain) null);
    }
}

假如有多个线程同时执行反射方法的调用,上面有说过每个线程持有一个自己的Method对象,当调用invoke方法后,每个线程都调用了acquireMethodAccessor()方法。
reflectionFactoryMethodFieldConstructor等类的父类AccessibleObject中的静态变量,在AccessibleObject初始化的时候就已经存在了,是由多线程共享的一个变量,现在假如有1000个线程同时调用了该方法,线程执行到该方法的时候就会在内存中通过asm生成1000份字节码和1000个MagicAccessorImpl的类及类加载器。这些类会占用方法区,不同的虚拟机回收策略可能会有所不同,所以这块可能会有性能问题。这里不要被1000个线程误导,如果缓存的是NativeAccessorImpl即使是一个线程执行1000次也会出现同样的结果!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值