反射 基础

什么是反射

JVM 可以在运行时动态地拿到类的详细信息,这允许在运行时才确认类的信息。

谁在用反射

首先,我们人肯定用得很少,或者说基本不用。用这个的多是一些支持自动化的组件,比如 Spring 和 Mybatis 这些框架。

从内存的角度说反射

反射的物理层基础

当 ClassLoader 加载一个类(注意是字节码)时,首先会把类的一些固有信息,例如类名、有哪些成员、成员方法的实现段、静态成员等,都会存到元空间,其格式是用Cpp写的,但是注意,只是存储格式是 Cpp 的,其成员名称、方法代码段原文是以 .class 存储的。

在将类的固有信息存入元空间之后,紧接着 JVM 会实例化一个java.lang.Class类型的对象存在堆区,这个对象同样用来存储类的固有信息,但和元空间的那个 Cpp 结构体的区别在于:元空间里那个存的是真正的数据java.lang.Class对象存的是指向元空间那片空间的Cpp指针,所以这个 Class 对象相当于一个代理

方法也是一样的,方法的实现段存在元空间java.lang.reflect.Method对象就存方法在元空间内真实存在的方法实现段的 Cpp 指针。

元空间内方法实现段的存储

首先,Java 代码被翻译成字节码被存入元空间,这里的字节码并非一整段字符串,而是按照一句一句存储的数组,就比如下面的 java 代码:

1
public void run(){int a = 1;}

这个方法的实现段int a = 1;被翻译成字节码就是:

1
2
3
iconst_1
istore_1
return

那在元空间内是怎么存这个方法的?(并非实际存储,只是模拟)

1
2
3
0x000000    iconst_1    <--- ConstMethod* _constMethod
0x000016 istore_1
0x000032 return

被实例化的 Method 对象内存在的方法指针ConstMethod* _constMethod;就是0x000000,代表这个代码段的字节码一句一句,存放在这个地址。

反射怎么执行方法?

当需要用反射的时候,可通过java.lang.reflect.Method对象调用invoke(args)方法,这一步可以是由我们自行编写代码发起的,也可以是各种框架内部写死的调用方法发起的。而这个方法在底层,实则是 JVM 主动调用了一个 由 Cpp 实现的 native方法,由该方法执行。native方法再到元空间内,根据方法的指针,查找到代码段的字节码,然后一句一句解释执行。

不过,考虑到反射方法执行的极高开销(jvm 调用native方法,运行时动态路由到方法地址),反射方法和普通方法类似,当成为热点方法后,JVM 会用字节码动态地拼凑出一个全新的类,用来直接调用这个方法,然后通过类加载器加载到内存中(当然,这个方法的实现段一样存在元空间)。之后的调用就简单了,JVM 不需要再调用底层 Cpp 代码动态路由方法地址,而是和普通方法一样处理,JVM 通过访问虚函数表,直接定位到方法的实现段,并加载到栈区解释执行。当然,如果实在太热,也会在超过调用次数阈值后,直接缓存为机器码,让 CPU 直接执行。

反射性能为什么不好?

从上面反射的执行就可以看出,反射方法之所以性能不如普通方法,已然有以下两大原因:

  • JVM 调用底层 Cpp 代码耗费性能;

  • Cpp 代码需要在 JVM 运行时,通过不断地路由才能确定方法的地址。

然而,即便反射方法在经过16次后,也即克服了上面两大问题,反射方法的性能会依然不如普通方法,这里还有三大原因:

  • invoke(Object ...) 方法的参数为可变参数,可变参数的处理相当麻烦,加重性能负担;

  • 方法权限问题:反射执行方法在编译时不会检查权限,因为反射是在 JVM 运行时才进行的,也就是说被 invoke 的方法有可能不是 public 的,而 private 方法原则上是不能被其他对象调用的,正因为有这种可能性,执行时底层会不断地问询鉴权是否有权执行这个方法,这又是性能负担。不过,可以使用method.setAccessible(true);,强制让反射方法获取权限,并且跳过鉴权的环节;

  • 无法内联:inline是优化执行效率的方法,普通方法内直接把方法代码段粘贴到调用的地方避免跳转地址的开销,然而反射方法做不到精确的预测,内联大概率失败,所以无法吃到这个 buff,性能自然就下来了。

场景

  • 动态代理。

  • 注解:需要利用反射读取注解对应的类、方法,才能知道这个注解是什么意思。

  • IoC & DI:基于动态代理和注解。

  • ORM 关系对象映射,通过获取类名拼接关键字和 sql ,从而自动化数据库操作。