vlambda博客
学习文章列表

源码角度分析Java反射实现原理

还记得在校期间参加校招面试,面试官问我那你说说Java的反射原理吧。顿时蒙蔽,当时大脑中只记得动态代理的原理是反射,Java的反射机制可以让我们运行时动态地调用某个对象的方法、新建对象实例、获取对象的属性等。巴拉巴拉说了说反射的概念以及反射的应用等,就和我说再见了,下面来一起探索一下Java反射的实现原理吧。
首先说一下反射机制的概念
反射机制是指在程序运行过程中,对任意一个类都能获取其所有属性和方法,并且对任意一个对象都能调用其任意一个方法。这种动态获取类和对象的信息,以及动态调用对象的方法的功能便是Java语言的反射机制。
说完反射的概念,谈谈反射的应用。
我们知道Java中的对象有编译时类型和运行时类型两种类型。编译时类型指的是声明对象时所采用的类型,运行时类型指为对象赋值时所采用的类型。例子如下:


Animal animal=new Cat();


在以上代码中,animal对象的编译时类型为Animal,运行时类型为Cat,因此无法在编译时获取Cat类中定义的方法。对于程序在编译期间无法获得对象和类的真实信息,只能通过运行时信息来获取该对象和类的信息,而这些信息便是通过反射机制来获取的,这便是Java反射机制的核心。
接下来谈谈反射的步骤
反射的步骤如下:
  1. 获取想要操作的类的对象,通过该类对象可以调用类的任意方法。

  2. 调用Class对象所对应的类中定义的方法

  3. 使用反射API来获取并调用类的属性和方法等

获取Class对象的三种方法:

1.调用某个对象的getClass方法以获取该类对应的Class对象

 

    Animal animal = new Animal();Class clz = animal.getClass();

2.调用某个类的class属性以获取该类对应的Class对象


    Class clz = Animal.class;

3.调用Class类中的forName静态方法,从而获取对应的Class对象

    Class clz=Class.forName("ClassPath");
通过反射创建对象有两种方式:

1.使用Class对象的newInstance方法创建该Class对象对应类的实例,这种方法要求该Class对象对应的类有默认的空构造器

 


    Class clz=Class.forName("ClassPath");Animal a=(Animal)clz.newInstance();


2.先使用Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance方法创建Class对象对应类的实例


    Constructor c = clazz.getDeclaredConstructor(String.class,int.class);Cat cat = (Cat) c.newInstance("美国短尾猫","2");
如何动态调用方法?
获取对象的Method,并调用Method的invoke方法。
  1. 获取Method对象:通过调用Class对象的getMethod(String name,Class<?>... parameterTypes)返回一个Method对象,他描述了此Class对象所表示的类或接口指定的公共成员方法。其中name参数是指所需方法的名称,parameterTypes参数是按照声明顺序标识该方法的形参类型的Class对象的一个数组。

  2. 调用invoke方法:通过调用Method对象的invoke方法来动态执行函数

    //首先获取类对象Class clz = Class.forName("Animal");//获取类对象的setName方法Method method = clz.getMethod("setName", String.class);//获取构造器对象Constructor constructor = clz.getConstructor();//根据构造器对象生成实体对象Animal animal = (Animal) constructor.newInstance();//通过方法对象的invoke函数对实体对象方法进行动态调用method.invoke(animal, "美国蓝白猫");
接下来咱们从源码角度来探索Java反射实现的原理。
 
public class ReflectionTest {
private static int count=0; public static void foo(){ new Exception((++count)+"次").printStackTrace(); }
public static void main(String[] args) throws Exception { Class<?> clz= Class.forName("ReflectionTest"); Method method=clz.getMethod("foo"); for(int i=0;i<20;i++){ method.invoke(null); } }}

对于下面的堆栈执行结果我们可以看到从第17次开始,函数调用方式出现了变化:
 
java.lang.Exception: 16次 at com.Reflection.foo(Reflection.java:13) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.Reflection.main(Reflection.java:20)java.lang.Exception: 17次 at com.Reflection.foo(Reflection.java:13) at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.Reflection.main(Reflection.java:20)

invoke源码如下:
 
@CallerSensitivepublic 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是一个接口,实现代码如下:
 
public interface MethodAccessor { Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException;}


我们可以看到MethodAccessor的实现类有3个,分别为DelegatingMethodAccessorImpl、MethodAccessorImpl以及NativeMethodAccessorImpl。通过堆栈我们可以看到在前16次调用的实现类NativeMethodAccessorImpl,之后调用的为GeneratedMethodAccessor1,那么接下来就让我们来探索一下NativeMethodAccessorImpl实现类。
 
class NativeMethodAccessorImpl extends MethodAccessorImpl { private final Method method; private DelegatingMethodAccessorImpl parent; private int numInvocations;
NativeMethodAccessorImpl(Method var1) { this.method = var1; }
public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException { if (++this.numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(this.method.getDeclaringClass())) { MethodAccessorImpl var3 = (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(var3); }
return invoke0(this.method, var1, var2); }
void setParent(DelegatingMethodAccessorImpl var1) { this.parent = var1; }
private static native Object invoke0(Method var0, Object var1, Object[] var2);}

我们可以看到在进行方法调用的过程中存在一个条件判断
++this.numInvocations > ReflectionFactory.inflationThreshold()
当在这个阈值之内的时候会调用invoke0方法,这是一个native函数,当超过这个阈值之后生成MethodAccessorGenerator对象之后通过ASM生成新的类sun.reflect.GeneratedMethodAccessor1,可以使用arthas工具查看生成的类的字节码,对字节码翻译之后的代码如下:
 
public class GeneratedMethodAccessor1 extends MethodAccessorImpl{ @Override public Object invoke(Object obj,Object[] args) throws IllegalArgumentException,InvocationTargetException{ ReflectTest.foo(); return null; }}

那么问题来了,为什么要设置一个阈值来进行两种方式的调用呢。其实这是基于性能的考虑,JNI native调用的方式要比动态生成类调用的方式慢20倍左右,但是由于第一次字节码生成的过程是比较慢的,如果反射仅调用一次的话,采用生成字节码的方式反而比native调用的方式慢了3-4倍。因此Java引入了inflation机制,当调用次数小于阈值时,采用native方法,没有额外类的生成、校验以及加载的开销;当超过阈值时,会使用ASM生成新类,保证后面的调用比native要快。
参考书籍:
王磊. Offer来了[M].北京:电子工业出版社,2020.
张亚. 深入理解JVM字节码[M].北京:机械工业出版社,2020.