反射底层原理

siyue ... 基础
  • 基础
大约 4 分钟

# 简介

反射机制是在运行状态中,对于任意一个类,都能够获得这个类的所有属性和方法,对于任意一个对象都能够调用它的任意一个属性和方法。这种在运行时动态的获取信息以及动态调用对象的方法的功能称为 Java 的反射机制。

# 原理

通常情况下我们是这么使用反射的:

Method method = XXX.class.getDeclaredMethod(xx,xx);
method.invoke(target,params)
1
2

# Method 获取

要调用首先要获取Method,而获取Method的逻辑是通过Class这个类来的,而关键的几个方法和属性如下:

class Class{
	SoftReference<ReflectionData<T>> reflectionData;
		
	public Method getDeclaredMethod(String name, Class<?>... parameterTypes);
	private static Method searchMethods(Method[] methods,String name, Class<?>[] parameterTypes);   
	private Method[] privateGetDeclaredMethods(boolean publicOnly);
}
1
2
3
4
5
6
7

其中privateGetDeclaredMethods方法从缓存或JVM中获取该Class中申明的方法列表,searchMethods方法将从返回的方法列表里找到一个匹配名称和参数的方法对象。privateGetDeclaredMethods中获取方法列表主要就是靠reflectionData

# reflectionData

这里主要存的是每次从jvm里获取到的一些类属性,比如方法,字段等。

在这里插入图片描述

这个属性主要是SoftReference的,也就是在某些内存比较苛刻的情况下是可能被回收的,不过正常情况下可以通过-XX:SoftRefLRUPolicyMSPerMB这个参数来控制回收的时机,一旦时机到了,只要GC发生就会将其回收,那回收之后意味着再有需求的时候要重新创建一个这样的对象,同时也需要从JVM里重新拿一份数据,那这个数据结构关联的Method,Field字段等都是重新生成的对象。

# copy()方法

在获取到Method对象之后,并不是直接返回,而是copy一份对象。

在这里插入图片描述

这里的root可看作是每一个方法唯一对应的Method,以后产生的每个Method都是从这里复制出去的。同所有的MethodmethodAccessor都指向同一个。

# Method 使用

有了Method之后,那就可以调用其invoke方法了,那先看看Method的几个关键信息

class Method{
    public Object invoke(Object obj, Object... args);
    private Method	root;
    private volatile MethodAccessor methodAccessor;
}
1
2
3
4
5

root属性其实上面已经说了,主要指向缓存里的Method对象,也就是当前这个Method对象其实是根据root这个Method构建出来的,因此存在一个root Method派生出多个Method的情况。

methodAccessor这个很关键了,其实Method.invoke方法就是调用methodAccessorinvoke方法,methodAccessor这个属性如果root本身已经有了,那就直接用rootmethodAccessor赋值过来,否则的话就创建一个

# MethodAccessor

methodAccessor本身只是一个接口

在这里插入图片描述

其实现主要有三种:

  • DelegatingMethodAccessorImpl
  • NativeMethodAccessorImpl
  • GeneratedMethodAccessorXXX

其中DelegatingMethodAccessorImpl是最终注入给MethodmethodAccessor的,也就是某个Method的所有的invoke方法都会调用到这个DelegatingMethodAccessorImpl.invoke,正如其名一样的,是做代理的,就是为了下面切换不同调用类使用的。所以最终生成的MethodAccessor就两种。

在这里插入图片描述

如果是NativeMethodAccessorImpl,那顾名思义,该实现主要是native实现的,而GeneratedMethodAccessorXXX是为每个需要反射调用的Method动态生成的类,后的XXX是一个数字,不断递增的

并且所有的方法反射都是先走NativeMethodAccessorImpl,默认调了15次之后,才生成一个GeneratedMethodAccessorXXX类,生成好之后就会走这个生成的类的invoke方法了。那么怎么定义是15次?到了15次又是怎么切换类的呢?为什么又需要切换调用的invoke类呢?

在生成methodAccessor的类ReflectionFactory中定义了阈值15

class ReflectionFactory{
	private static int     inflationThreshold = 15;
}
1
2
3

这个15当然也不是一尘不变的,我们可以通过-Dsun.reflect.inflationThreshold=xxx来指定,我们还可以通过-Dsun.reflect.noInflation=true来直接绕过上面的15次NativeMethodAccessorImpl调用,和-Dsun.reflect.inflationThreshold=0的效果一样的

在这里插入图片描述

这个值在NativeMethodAccessorImpl调用invoke方法时候会判断是否大于15,超过以后会生成GeneratedMethodAccessorXXX,其实就是直接调用目标对象的具体方法了,和正常的方法调用没什么区别。这样下次Method.invoke就会调到这个新创建的MethodAccessor里了。

至于为什么必须走 NativeMethodAccessorImp.invoke,源码给的解释是通过加载字节码以实现Method.invoke()Constructor.newInstance()成本是第一次通过本机代码调用(本机代码应该是native方法)的成本的3-4倍。这会增加某些密集使用反射的应用程序的启动时间(但每个类仅一次)为了避免这种情况,我们在方法和构造函数的最初几次调用中重用了现有的JVM入口点,然后才是基于字节码的实现。

参考: 1、深入分析Java方法反射的实现原理 (opens new window) 2、假笨说-从一起GC血案谈到反射原理 (opens new window)