大家都说 Java 反射效率低,你有知道这个原因?
Posted Java_Pluto
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了大家都说 Java 反射效率低,你有知道这个原因?相关的知识,希望对你有一定的参考价值。
预备知识
了解 Java 反射基本用法
看完本文可以达到什么程度
了解 Java 反射原理及 Java 反射效率低的原因
文章概览
我们在 Java 开发中,难免会接触到反射,而在一些框架中,反射的运用更是常见。我相信,每次提到反射,大家的第一反应一定是反射效率低,尽量少使用。
但是反射的效率到底低多少?反射效率低的原因在哪里?
这篇文章就来探索一下这些问题。
由于本机上安装的是 openjdk 12,所以这里就使用 openjdk 12 源码进行分析。
我们先看结论,然后分析一下 Java 反射的原理,过程中大家可以根据结论,对源码做一些思考,然后再根据原理中的一些实现,看看 Java 反射效率低的原因。
零、先放结论
Java 反射效率低主要原因是:
- Method#invoke 方法会对参数做封装和解封操作
- 需要检查方法可见性
- 需要校验参数
- 反射方法难以内联
- JIT 无法优化
一、Java 反射原理–获取要反射的方法
1.1 反射的使用
我们先来看看 Java 反射使用的一段代码:
public class RefTest {
public static void main(String[] args) {
try {
Class clazz = Class.forName("com.zy.java.RefTest");
Object refTest = clazz.newInstance();
Method method = clazz.getDeclaredMethod("refMethod");
method.invoke(refTest);
} catch (Exception e) {
e.printStackTrace();
}
}
public void refMethod() {
}
}
我们在调用反射时,首先会创建 Class 对象,然后获取其 Method 对象,调用 invoke 方法。
获取反射方法时,有两个方法,getMethod 和 getDeclaredMethod,我们就从这两个方法开始,一步步看下反射的原理。
接下来就进入代码分析,大家做好准备。
1.2 getMethod / getDeclaredMethod
这里我们先整体看一下 getMethod 和 getDeclaredMethod 的实现。
class Class {
@CallerSensitive
public Method getMethod(String name, Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
Objects.requireNonNull(name);
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
// 1. 检查方法权限
checkMemberAccess(sm, Member.PUBLIC, Reflection.getCallerClass(), true);
}
// 2. 获取方法
Method method = getMethod0(name, parameterTypes);
if (method == null) {
throw new NoSuchMethodException(methodToString(name, parameterTypes));
}
// 3. 返回方法的拷贝
return getReflectionFactory().copyMethod(method);
}
@CallerSensitive
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
Objects.requireNonNull(name);
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
// 1. 检查方法是权限
checkMemberAccess(sm, Member.DECLARED, Reflection.getCallerClass(), true);
}
// 2. 获取方法
Method method = searchMethods(privateGetDeclaredMethods(false), name, parameterTypes);
if (method == null) {
throw new NoSuchMethodException(methodToString(name, parameterTypes));
}
// 3. 返回方法的拷贝
return getReflectionFactory().copyMethod(method);
}
}
从上面的代码,我们可以看到,获取方法的流程分三步走:
- 检查方法权限
- 获取方法 Method 对象
- 返回方法的拷贝
这里主要有两个区别:
- getMethod 中 checkMemberAccess 传入的是 Member.PUBLIC,而 getDeclaredMethod
传入的是 Member.DECLARED这两个值有什么区别呢?我们看下代码中的注释:
interface Member {
/**
* Identifies the set of all public members of a class or interface,
* including inherited members.
*/
public static final int PUBLIC = 0;
/**
* Identifies the set of declared members of a class or interface.
* Inherited members are not included.
*/
public static final int DECLARED = 1;
}
注释里清楚的解释了 PUBLIC 和 DECLARED 的不同,PUBLIC 会包括所有的 public 方法,包括父类的方法,而 DECLARED 会包括所有自己定义的方法,public,protected,private 都在此,但是不包括父类的方法。
这也正是 getMethod 和 getDeclaredMethod 的区别。
- getMethod 中获取方法调用的是 getMethod0,而 getDeclaredMethod 获取方法调用的是 privateGetDeclaredMethods关于这个区别,这里简单提及一下,后面具体分析代码。
privateGetDeclaredMethods 是获取类自身定义的方法,参数是 boolean publicOnly,表示是否只获取公共方法。
private Method[] privateGetDeclaredMethods(boolean publicOnly) {
//...
}
而 getMethod0 会递归查找父类的方法,其中会调用到 privateGetDeclaredMethods 方法。
既然我们上面看了 getMethod 和 getDeclaredMethod 的区别,我们自然选择 getMethod 方法进行分析,这样可以走到整个流程。
1.3 getMethod 方法
getMethod 方法流程如下图:
class Class {
public Method getMethod(String name, Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
Objects.requireNonNull(name);
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
// 1. 检查方法权限
checkMemberAccess(sm, Member.PUBLIC, Reflection.getCallerClass(), true);
}
// 2. 获取方法 Method 对象
Method method = getMethod0(name, parameterTypes);
if (method == null) {
throw new NoSuchMethodException(methodToString(name, parameterTypes));
}
// 3. 返回方法拷贝
return getReflectionFactory().copyMethod(method);
}
}
我们上面说到获取方法分三步走:
- 检查方法权限
- 获取方法 Method 对象
- 返回方法的拷贝
我们先看看检查方法权限做了些什么事情。
1.3.1 checkMemberAccess
class Class {
private void checkMemberAccess(SecurityManager sm, int which,
Class<?> caller, boolean checkProxyInterfaces) {
/* Default policy allows access to all {@link Member#PUBLIC} members,
* as well as access to classes that have the same class loader as the caller.
* In all other cases, it requires RuntimePermission("accessDeclaredMembers")
* permission.
*/
final ClassLoader ccl = ClassLoader.getClassLoader(caller);
if (which != Member.PUBLIC) {
final ClassLoader cl = getClassLoader0();
if (ccl != cl) {
sm.checkPermission(SecurityConstants.CHECK_MEMBER_ACCESS_PERMISSION);
}
}
this.checkPackageAccess(sm, ccl, checkProxyInterfaces);
}
}
在这里可以看到,对于非 Member.PUBLIC 的访问,会增加一项检测,SecurityManager.checkPermission(SecurityConstants.CHECK_MEMBER_ACCESS_PERMISSION); 这项检测需要运行时申请 RuntimePermission(“accessDeclaredMembers”)。
这里就不继续往下看了,方法整体是在检查是否可以访问对象成员。
接着看下是如何获取方法的 Method 对象。
1.3.2 getMethod0
class Class {
private Method getMethod0(String name, Class<?>[] parameterTypes) {
PublicMethods.MethodList res = getMethodsRecursive(
name,
parameterTypes == null ? EMPTY_CLASS_ARRAY : parameterTypes,
/* includeStatic */ true);
return res == null ? null : res.getMostSpecific();
}
}
这里是通过 getMethodsRecursive 获取到 MethodList 对象,然后通过 MethodList#getMostSpecific 方法筛选出对应的方法。
MethodList#getMOstSpecific 会筛选返回值类型最为具体的方法,至于为什么会有返回值的区别,后面会讲到。
(这里的具体,指的是有两个方法,返回值分别是 Child 和 Parent,Child 继承自 Parent,这里会筛选出返回值为 Child 的方法)。
接着看 getMethodsRecursive 方法,是如何获取方法的。
1.3.3 getMethodsRecursive
class Class {
private PublicMethods.MethodList getMethodsRecursive(String name,
Class<?>[] parameterTypes,
boolean includeStatic) {
// 1. 获取自己的 public 方法
Method[] methods = privateGetDeclaredMethods(/* publicOnly */ true);
// 2. 筛选符合条件的方法,构造 MethodList 对象
PublicMethods.MethodList res = PublicMethods.MethodList
.filter(methods, name, parameterTypes, includeStatic);
// 找到方法,直接返回
if (res != null) {
return res;
}
// 3. 没有找到方法,就获取其父类,递归调用 getMethodsRecursive 方法
Class<?> sc = getSuperclass();
if (sc != null) {
res = sc.getMethodsRecursive(name, parameterTypes, includeStatic);
}
// 4. 获取接口中对应的方法
for (Class<?> intf : getInterfaces(/* cloneArray */ false)) {
res = PublicMethods.MethodList.merge(
res, intf.getMethodsRecursive(name, parameterTypes,
/* includeStatic */ false));
}
return res;
}
}
这里获取方法有四个步骤:
- 通过 privateGetDeclaredMethods 获取自己所有的 public 方法
- 通过 MethodList#filter 查找 方法名,参数相同的方法,如果找到,直接返回
- 如果自己没有实现对应的方法,就去父类中查找对应的方法
- 查找接口中对应的方法
通过上面四个步骤,最终获取到的是一个 MethodList 对象,是一个链表结点,其 next 指向下一个结点。也就是说,这里获取到的 Method 会有多个。
这里稍微解释一下,在我们平时编写 Java 代码时,同一个类是不能有方法名和方法参数都相同的方法的,而实际上,在 JVM 中,一个方法签名是和 返回值,方法名,方法参数 三者相关的。
也就是说,在 JVM 中,可以存在 方法名和方法参数都相同,但是返回值不同的方法。
所以这里返回的是一个方法链表。
所以上面最终返回方法时会通过 MethodList#getMostSpecific 进行返回值的筛选,筛选出返回值类型最具体的方法。
这里我们先暂停回顾一下整体的调用链路:
getMethod -> getMethod0 -> getMethodsRecursive -> privateGetDeclaredMethods
通过函数调用,最终会调用到 privateGetDeclaredMethods 方法,也就是真正获取方法的地方。
1.3.4 privateGetDeclaredMethods
class Class {
private Method[] privateGetDeclaredMethods(boolean publicOnly) {
Method[] res;
// 1. 通过缓存获取 Method[]
ReflectionData<T> rd = reflectionData();
if (rd != null) {
res = publicOnly ? rd.declaredPublicMethods : rd.declaredMethods;
if (res != null) return res;
}
// 2. 没有缓存,通过 JVM 获取
res = Reflection.filterMethods(this, getDeclaredMethods0(publicOnly));
if (rd != null) {
if (publicOnly) {
rd.declaredPublicMethods = res;
} else {
rd.declaredMethods = res;
}
}
return res;
}
}
在 privateGetDeclaredMethods 获取方法时,有两个步骤:
- relectionData 通过缓存获取
- 如果缓存没有命中的话,通过 getDeclaredMethods0 获取方法
先看看 relectionData 方法:
class Class {
private ReflectionData<T> reflectionData() {
SoftReference<ReflectionData<T>> reflectionData = this.reflectionData;
int classRedefinedCount = this.classRedefinedCount;
ReflectionData<T> rd;
if (reflectionData != null &&
(rd = reflectionData.get()) != null &&
rd.redefinedCount == classRedefinedCount) {
return rd;
}
// else no SoftReference or cleared SoftReference or stale ReflectionData
// -> create and replace new instance
return newReflectionData(reflectionData, classRedefinedCount);
}
}
在 Class 中会维护一个 ReflectionData 的软引用,作为反射数据的缓存。
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;
// Cached names
String simpleName;
String canonicalName;
static final String NULL_SENTINEL = new String();
// Value of classRedefinedCount when we created this ReflectionData instance
final int redefinedCount;
}
可以看到,保存了 Class 中的属性和方法。
如果缓存为空,就会通过 getDeclaredMethods0 从 JVM 中查找方法。
getDeclaredMethods0 是一个 native 方法,这里暂时先不看。
通过上面几个步骤,就获取到 Method 数组。
这就是 getMethod 方法的整个实现了。
我们再回过头看一下 getDeclaredMethod 方法的实现,通过 privateGetDeclaredMethods 获取方法以后,会通过 searchMethods 对方法进行筛选。
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
// ...
Method method = searchMethods(privateGetDeclaredMethods(false), name, parameterTypes);
// ...
}
searchMethods 方法实现比较简单,就是对比方法名,参数,方法返回值。
class Class {
private static Method searchMethods(Method[] methods,
String name,
Class<?>[] parameterTypes)
{
ReflectionFactory fact = getReflectionFactory();
Method res = null;
for (Method m : methods) {
// 比较方法名
if (m.getName().equals(name)
// 比较方法参数
&& arrayContentsEq(parameterTypes,
fact.getExecutableSharedParameterTypes(m))
// 比较返回值
&& (res == null
|| (res.getReturnType() != m.getReturnType()
&& res.getReturnType().isAssignableFrom(m.getReturnType()))))
res = m;
}
return res;
}
}
1.3.5 Method#copy
在获取到对应方法以后,并不会直接返回,而是会通过 getReflectionFactory().copyMethod(method); 返回方法的一个拷贝。
最终调用的是 Method#copy,我们来看看其实现。
class 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;
}
}
会 new 一个 Method 实例并返回。
这里有两点要注意:
- 设置 root = this
- 会给 Method 设置 MethodAccessor,用于后面方法调用。也就是所有的 Method 的拷贝都会使用同一份 methodAccessor。
通过上面的步骤,就获取到了需要反射的方法。
我们再回顾一下之前的流程。
二、Java 反射原理–调用反射方法
获取到方法以后,通过 Method#invoke 调用方法。
class Method {
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
if (!override) {
Class<?> caller = Reflection.getCallerClass();
// 1. 检查权限
checkAccess(caller, clazz,
Modifier.isStatic(modifiers) ? null : obj.getClass(),
modifiers);
}
// 2. 获取 MethodAccessor
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
// 创建 MethodAccessor
ma = acquireMethodAccessor();
}
// 3. 调用 MethodAccessor.invoke
return ma.invoke(obj, args);
}
}
invoke 方法的实现,分为三步:
2.1 检查是否有权限调用方法
这里对 override 变量进行判断,如果 override == true,就跳过检查
我们通常在 Method#invoke 之前,会调用 Method#setAccessible(true),就是设置 override 值为 true。
2.2 获取 MethodAccessor
在上面获取 Method 的时候我们讲到过,Method#copy 会给 Method 的 methodAccessor 赋值。所以这里的 methodAccessor 就是拷贝时使用的 MethodAccessor。
如果 ma 为空,就去创建 MethodAccessor。
class Method {
private MethodAccessor acquireMethodAccessor() {
为什么大家都说 SELECT * 效率低
在win7旗舰版上安装VirtualBox虚拟机,出现在虚拟XP系统中不能识别USB设备,有知道原因的吗?
面试了3家公司,都问我:为什么大家都说 SELECT * 效率低?
C#,winform中,listview在虚拟模式中,怎么放置在CheckBox?是放置图片还是控件呢? 大家有知道的没