Java 리플렉션 속도에 대해 이야기해 보세요.

coldplay.xixi
풀어 주다: 2020-08-28 16:50:02
앞으로
2297명이 탐색했습니다.

Java 리플렉션 속도에 대해 이야기해 보세요.

【관련 학습 추천:Java 기본 튜토리얼

Reflection은 좋은가요, 나쁜가요?

Java의 Reflection에 대해 말하자면, 초보자는 Reflection의 다양한 고급 기능을 처음 접할 때 큰 설렘을 표현하는 경우가 많습니다. 반사가 필요하지 않은 일부 장면에서는 반사를 강제로 사용하여 "과시"할 수도 있습니다. 더 많은 경험을 가진 노인들은 성찰을 볼 때 영혼 속으로 세 가지 질문을 자주 던집니다. 왜 성찰을 사용합니까? 반성하면 성능이 저하되지 않나요? 이 문제를 해결할 다른 방법이 있나요?

그래서 오늘은 성찰이 성과에 얼마나 영향을 미치는지에 대해 심도 있게 논의해 보겠습니다. 그런데 왜 반사가 성능에 영향을 미치는지 살펴보겠습니다.

코딩 실험

특정 원리를 분석하기 전에 먼저 코드 작성과 실험을 통해 결론을 도출할 수 있습니다.

Reflection에는 인스턴스 생성, 변수 속성 가져오기/설정, 메서드 호출 등과 같은 다양한 유형의 작업이 포함될 수 있습니다. 간단히 생각해보면 인스턴스 생성이 다른 작업보다 성능에 더 큰 영향을 미친다고 판단하여 인스턴스 생성을 실험에 사용합니다.

다음 코드에서는InnerClass클래스를 정의하고newreflection을 사용하여MAX_TIMES를 생성하는지 테스트합니다. > code> 인스턴스를 실행하고 경과 시간을 출력합니다.InnerClass,我们测试分别使用new反射来生成MAX_TIMES个实例,并打印出耗时时间。

public class MainActivity extends AppCompatActivity { private static final String TAG = "MainAc"; private final int MAX_TIMES = 100 * 1000; private InnerClass innerList[]; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); innerList = new InnerClass[MAX_TIMES]; long startTime = SystemClock.elapsedRealtime(); for (int i=0; i < MAX_TIMES; i++) { innerList[i] = new InnerClass(); } Log.e(TAG, "totalTime: " + (SystemClock.elapsedRealtime() - startTime)); long startTime2 = SystemClock.elapsedRealtime(); for (int i=0; i < MAX_TIMES; i++) { innerList[i] = newInstanceByReflection(); } Log.e(TAG, "totalTime2: " + (SystemClock.elapsedRealtime() - startTime2)); } public InnerClass newInstanceByReflection() { Class clazz = InnerClass.class; try { return (InnerClass) clazz.getDeclaredConstructor().newInstance(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } return null; } static class InnerClass { } }复制代码
로그인 후 복사

输出日志:

2020-03-19 22:34:49.738 2151-2151/? E/MainAc: totalTime: 15 2020-03-19 22:34:50.409 2151-2151/? E/MainAc: totalTime2: 670复制代码
로그인 후 복사

使用反射生成 10万 个实例,耗时 670ms,明显高于直接使用new关键字的 15ms,所以反射性能低。别急,这个结论总结的还有点早,我们将要生成的实例总数改为 1000个试试,输出日志:

2020-03-19 22:39:21.287 3641-3641/com.example.myapplication E/MainAc: totalTime: 2 2020-03-19 22:39:21.296 3641-3641/com.example.myapplication E/MainAc: totalTime2: 9复制代码
로그인 후 복사

使用反射生成 1000 个实例,虽然需要9ms,高于new的 2ms,但是 9ms 和 2ms 的差距本身肉眼不可见,而且通常我们在业务中写的反射一般来说执行频率也未必会超过 1000 次,这种场景下,我们还能理直气壮地说反射性能很低么?

很显然,不能。

除了代码执行耗时,我们再看看反射对内存的影响。我们仍然以生成 10万 个实例为目标,对上述代码做略微改动,依次只保留new方式和反射方式,然后运行程序,观察内存占用情况。

Java 리플렉션 속도에 대해 이야기해 보세요.

使用new方式

Java 리플렉션 속도에 대해 이야기해 보세요.

使用反射

对比两图,我们可以看到第二张图中多了很多ConstructorClass对象实例,这两部分占用的内存2.7M。因此,我们可以得出结论,反射会产生大量的临时对象,并且会占用额外内存空间。

刨根问底:反射原理是什么

我们以前面试验中反射生成实例的代码为入口。

首先回顾下虚拟机中类的生命周期:加载,连接(验证,准备,解析),初始化,使用,卸载。在加载的过程 中,虚拟机会把类的字节码转换成运行时数据结构,并保存在方法区,在内存中会生成一个代表这个类数据结构的 java.lang.Class 对象,后续访问这个类的数据结构就可以通过这个 Class 对象来访问。

public InnerClass newInstanceByReflection() { // 获取虚拟机中 InnerClass 类的 Class 对象 Class clazz = InnerClass.class; try { return (InnerClass) clazz.getDeclaredConstructor().newInstance(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } return null; }复制代码
로그인 후 복사

代码中clazz.getDeclaredConstructor()用于获取类中定义的构造方法,由于我们没有显式定义构造方法,所以会返回编译器为我们自己生成的默认无参构造方法。

下面我们看下getDeclaredConstructor是如何返回构造方法的。以下均以 jdk 1.8代码为源码。

@CallerSensitivepublic Constructor getDeclaredConstructor(Class... parameterTypes) throws NoSuchMethodException, SecurityException { // 权限检查 checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true); return getConstructor0(parameterTypes, Member.DECLARED); }复制代码
로그인 후 복사

getDeclaredConstructor方法首先做了权限检查,然后直接调用getConstructor0方法。

private Constructor getConstructor0(Class[] parameterTypes, int which) throws NoSuchMethodException{ // privateGetDeclaredConstructors 方法是获取所有的构造方法数组 Constructor[] constructors = privateGetDeclaredConstructors((which == Member.PUBLIC)); // 遍历所有的构造方法数组,根据传入的参数类型依次匹配,找到合适的构造方法后就会拷贝一份作为返回值 for (Constructor constructor : constructors) { if (arrayContentsEq(parameterTypes, constructor.getParameterTypes())) { // 拷贝构造方法 return getReflectionFactory().copyConstructor(constructor); } } // 没有找到的话,就抛出异常 throw new NoSuchMethodException(getName() + "." + argumentTypesToString(parameterTypes)); }复制代码
로그인 후 복사

getConstructor0方法主要做了两件事:

  • 获取所有构造方法组成的数组
  • 遍历构造方法数组,找到匹配的

遍历匹配没啥好说的,我们重点看下第一件事,怎么获取的所有构造方法数组,也就是这个方法privateGetDeclaredConstructors

private Constructor[] privateGetDeclaredConstructors(boolean publicOnly) { checkInitted(); Constructor[] res; // 获取缓存的 ReflectionData 数据 ReflectionData rd = reflectionData(); // 如果缓存中有 ReflectionData,就先看看 ReflectionData 中的 publicConstructors 或 declaredConstructors是否为空 if (rd != null) { res = publicOnly ? rd.publicConstructors : rd.declaredConstructors; if (res != null) return res; } // 如果没有缓存,或者缓存中构造方法数组为空 // No cached value available; request value from VM // 对接口类型的字节码特殊处理 if (isInterface()) { @SuppressWarnings("unchecked") // 如果是接口类型,那么生成一个长度为0的构造方法数组 Constructor[] temporaryRes = (Constructor[]) new Constructor[0]; res = temporaryRes; } else { // 如果不是接口类型,就调用 getDeclaredConstructors0 获取构造方法数组 res = getDeclaredConstructors0(publicOnly); } // 获取到构造方法数组后,再赋值给缓存 ReflectionData 中的对应属性 if (rd != null) { if (publicOnly) { rd.publicConstructors = res; } else { rd.declaredConstructors = res; } } return res; }复制代码
로그인 후 복사

上述代码中我已经对关键代码进行了注释,在讲解整个流程之前,我们看到了一个陌生的类型ReflectionData。它对应的数据结构是:

private static class ReflectionData { volatile Field[] declaredFields; volatile Field[] publicFields; volatile Method[] declaredMethods; volatile Method[] publicMethods; volatile Constructor[] declaredConstructors; volatile Constructor[] publicConstructors; // Intermediate results for getFields and getMethods volatile Field[] declaredPublicFields; volatile Method[] declaredPublicMethods; volatile Class[] interfaces; // Value of classRedefinedCount when we created this ReflectionData instance final int redefinedCount; ReflectionData(int redefinedCount) { this.redefinedCount = redefinedCount; } }复制代码
로그인 후 복사

ReflectionData这个类就是用来保存从虚拟机中获取到的一些数据。同时我们可以看到所有反射属性都使用了volatile关键字修饰。

获取缓存的ReflectionData数据是通过调用reflectionData()

// 定义在 Class 类中的反射缓存对象private volatile transient SoftReference> reflectionData;private ReflectionData reflectionData() { SoftReference> reflectionData = this.reflectionData; int classRedefinedCount = this.classRedefinedCount; ReflectionData rd; if (useCaches && 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); }复制代码
로그인 후 복사
로그인 후 복사

출력 로그:
private ReflectionData newReflectionData(SoftReference> oldReflectionData, int classRedefinedCount) { // 如果不允许使用缓存,直接返回 null if (!useCaches) return null; while (true) { ReflectionData rd = new ReflectionData<>(classRedefinedCount); // try to CAS it... if (Atomic.casReflectionData(this, oldReflectionData, new SoftReference<>(rd))) { return rd; } // else retry oldReflectionData = this.reflectionData; classRedefinedCount = this.classRedefinedCount; if (oldReflectionData != null && (rd = oldReflectionData.get()) != null && rd.redefinedCount == classRedefinedCount) { return rd; } } }复制代码
로그인 후 복사
로그인 후 복사
리플렉션을 사용하여 100,000개의 인스턴스를 생성하는 데 670ms가 소요됩니다. 이는 new키워드를 직접 사용할 때의 15ms보다 훨씬 길어서 리플렉션 성능이 낮습니다. 걱정하지 마세요. 이 결론은 생성할 총 인스턴스 수를 1000으로 변경해 보겠습니다. 출력 로그는 다음과 같습니다.
private native Constructor[] getDeclaredConstructors0(boolean publicOnly);复制代码
로그인 후 복사
로그인 후 복사
1000개의 인스턴스를 생성하려면 리플렉션을 사용하세요. 단, 9ms가 더 걸립니다. new2ms이지만 9ms와 2ms의 차이는 육안으로는 보이지 않으며 일반적으로 우리가 비즈니스에서 작성하는 리플렉션은 1,000회 이상 실행되지 않을 수 있습니다. 반사 성능이 아주 낮다는 건가요? 분명히 그렇지 않습니다. 코드를 실행하는 데 걸리는 시간 외에도 리플렉션이 메모리에 미치는 영향을 살펴보겠습니다. 우리는 여전히 100,000개의 인스턴스를 생성하는 것을 목표로 하고, new메서드와 리플렉션 메서드만 순서대로 유지하면서 위 코드를 약간 변경한 다음 프로그램을 실행하여 메모리 사용량을 관찰합니다. <그림> Java 리플렉션 속도에 대해 이야기해 보세요.
메서드 사용
Java 리플렉션 속도에 대해 이야기해 보세요.
반사 사용 두 그림을 비교하면 두 번째 그림에 더 많은 ConstructorClass개체 인스턴스가 있음을 알 수 있습니다. 이 두 부분은 2.7M의 메모리를 차지합니다. 따라서 리플렉션은 많은 수의 임시 개체를 생성하고 추가 메모리 공간을 차지한다는 결론을 내릴 수 있습니다.

본질적으로 살펴보겠습니다. 반사의 원리는 무엇인가요? 이전 실험의 반사 생성 인스턴스 코드를 다음과 같이 사용합니다. 진입점. 먼저 가상 머신에서 클래스의 수명 주기인 로딩, 연결(검증, 준비, 구문 분석), 초기화, 사용 및 제거를 검토합니다. 로딩 과정에서 가상머신은 클래스의 바이트코드를 런타임 데이터 구조로 변환하여 메소드 영역에 저장한다. 메모리에는 이 클래스의 데이터 구조를 나타내는 java.lang.Class 객체가 생성된다. 이후에 이 클래스 객체를 통해 데이터 구조에 액세스할 수 있습니다.
void getDeclaredConstructors0(Frame * frame){ // Frame 可以理解为调用native方法时,java层传递过来的数据的一种封装 LocalVars * vars = frame->localVars; Object * classObj = getLocalVarsThis(vars); // 取得java方法的入参 bool publicOnly = getLocalVarsBoolean(vars, 1); uint16_t constructorsCount = 0; // 获取要查询的类的 Class 对象 Class * c = classObj->extra; // 获取这个类的所有构造方法,且数量保存在 constructorsCount 中 Method* * constructors = getClassConstructors(c, publicOnly, &constructorsCount); // 获取 java 方法调用所属的 classLoader ClassLoader * classLoader = frame->method->classMember.attachClass->classLoader; // 拿到 Constructor 对应的 class 对象 Class * constructorClass = loadClass(classLoader, "java/lang/reflect/Constructor"); //创建一个长度为 constructorsCount 的数组保存构造方法 Object * constructorArr = newArray(arrayClass(constructorClass), constructorsCount); pushOperandRef(frame->operandStack, constructorArr); // 后面是具体的赋值逻辑。将native中的Method对象转化为java层的Constructor对象 if (constructorsCount > 0) { Thread * thread = frame->thread; Object* * constructorObjs = getObjectRefs(constructorArr); Method * constructorInitMethod = getClassConstructor(constructorClass, _constructorConstructorDescriptor); for (uint16_t i = 0; i < constructorsCount; i++) { Method * constructor = constructors[i]; Object * constructorObj = newObject(constructorClass); constructorObj->extra = constructor; constructorObjs[i] = constructorObj; OperandStack * ops = newOperandStack(9); pushOperandRef(ops, constructorObj); pushOperandRef(ops, classObj); pushOperandRef(ops, toClassArr(classLoader, methodParameterTypes(constructor), constructor->parsedDescriptor->parameterTypesCount)); if (constructor->exceptions != NULL) pushOperandRef(ops, toClassArr(classLoader, methodExceptionTypes(constructor), constructor->exceptions->number_of_exceptions)); else pushOperandRef(ops, toClassArr(classLoader, methodExceptionTypes(constructor), 0)); pushOperandInt(ops, constructor->classMember.accessFlags); pushOperandInt(ops, 0); pushOperandRef(ops, getSignatureStr(classLoader, constructor->classMember.signature)); // signature pushOperandRef(ops, toByteArr(classLoader, constructor->classMember.annotationData, constructor->classMember.annotationDataLen)); pushOperandRef(ops, toByteArr(classLoader, constructor->parameterAnnotationData, constructor->parameterAnnotationDataLen)); Frame * shimFrame = newShimFrame(thread, ops); pushThreadFrame(thread, shimFrame); // init constructorObj InvokeMethod(shimFrame, constructorInitMethod); } } }复制代码
로그인 후 복사
로그인 후 복사
코드에서는 클래스에 정의된 생성자 메서드를 가져오는 데clazz.getDeclaredConstructor()를 사용합니다. 생성자 메서드를 명시적으로 정의하지 않으므로 인수가 없는 기본 생성자를 반환합니다. 컴파일러가 스스로 생성한 메소드입니다.getDeclaredConstructor가 생성자를 어떻게 반환하는지 살펴보겠습니다. 다음은 모두 jdk 1.8 코드를 소스코드로 기반으로 한 것입니다.
Method* * getClassConstructors(Class * self, bool publicOnly, uint16_t * constructorsCount){ // 分配大小为 sizeof(Method) 的长度为 methodsCount 的连续内存地址,即数组 Method* * constructors = calloc(self->methodsCount, sizeof(Method)); *constructorsCount = 0; // 在native 层,构造方法和普通方法都存在 methods 中,逐一遍历 for (uint16_t i = 0; i < self->methodsCount; i++) { Method * method = self->methods + i; // 判断是否是构造方法 if (isMethodConstructor(method)) { // 检查权限 if (!publicOnly || isMethodPublic(method)) { // 符合条件的构造方法依次存到数组中 constructors[*constructorsCount] = method; (*constructorsCount)++; } } } return constructors; }复制代码
로그인 후 복사
로그인 후 복사
getDeclaredConstructor메서드는 먼저 권한을 확인한 다음getConstructor0메서드를 직접 호출합니다.
bool isMethodConstructor(Method * self){ return !isMethodStatic(self) && strcmp(self->classMember.name, "") == 0; }复制代码
로그인 후 복사
로그인 후 복사
getConstructor0이 메소드는 주로 두 가지 작업을 수행합니다:
  • 모든 구성 메소드로 구성된 배열 가져오기
  • 구성 메소드 배열을 탐색하고 일치하는 항목 찾기 one
  • li>
순회 일치에 대해서는 말할 것이 없습니다. 먼저 모든 생성자의 배열을 얻는 방법인privateGetDeclaredConstructors메서드에 집중하겠습니다.
Class * loadNonArrayClass(ClassLoader * classLoader, const char * className){ int32_t classSize = 0; char * classContent = NULL; Class * loadClass = NULL; classSize = readClass(className, &classContent); if (classSize > 0 && classContent != NULL){#if 0 printf("class size:%d,class data:[", classSize); for (int32_t i = 0; i < classSize; i++) { printf("0x%02x ", classContent[i]); } printf("]\n");#endif } if (classSize <= 0) { printf("Could not found target class\n"); exit(127); } // 解析字节码文件 loadClass = parseClassFile(classContent, classSize); loadClass->classLoader = classLoader; // 加载 defineClass(classLoader, loadClass); // 链接 linkClass(classLoader, loadClass); //printf("[Loaded %s\n", loadClass->name); return loadClass; }复制代码
로그인 후 복사
로그인 후 복사
위 코드에 키코드를 주석으로 달았는데, 전체 과정을 설명하기 전에, 우리는 생소한 유형의ReflectionData를 보았습니다. 해당 데이터 구조는 다음과 같습니다.
Class * parseClassFile(char * classContent, int32_t classSize){ ClassFile * classFile = NULL; classFile = parseClassData(classContent, classSize); return newClass(classFile); }复制代码
로그인 후 복사
로그인 후 복사
ReflectionData이 클래스는 가상 머신에서 얻은 일부 데이터를 저장하는 데 사용됩니다. 동시에 모든 반사 속성이휘발성키워드로 수정되는 것을 볼 수 있습니다.reflectionData()메서드를 호출하여 캐시된ReflectionData데이터를 가져옵니다.
// 定义在 Class 类中的反射缓存对象private volatile transient SoftReference> reflectionData;private ReflectionData reflectionData() { SoftReference> reflectionData = this.reflectionData; int classRedefinedCount = this.classRedefinedCount; ReflectionData rd; if (useCaches && 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); }复制代码
로그인 후 복사
로그인 후 복사

我们可以看到reflectionData实际上是一个软引用,软引用会在内存不足的情况下被虚拟机回收,所以reflectionData()方法在开始的地方,先判断了是否可以使用缓存以及缓存是否失效,如果失效了,就会调用newReflectionData方法生成一个新的ReflectionData实例。

接下来看看newReflectionData方法。

private ReflectionData newReflectionData(SoftReference> oldReflectionData, int classRedefinedCount) { // 如果不允许使用缓存,直接返回 null if (!useCaches) return null; while (true) { ReflectionData rd = new ReflectionData<>(classRedefinedCount); // try to CAS it... if (Atomic.casReflectionData(this, oldReflectionData, new SoftReference<>(rd))) { return rd; } // else retry oldReflectionData = this.reflectionData; classRedefinedCount = this.classRedefinedCount; if (oldReflectionData != null && (rd = oldReflectionData.get()) != null && rd.redefinedCount == classRedefinedCount) { return rd; } } }复制代码
로그인 후 복사
로그인 후 복사

newReflectionData中使用volatile + 死循环 + CAS 机制保证线程安全。注意到这里的死循环每执行一次都会构造一个新的ReflectionData实例。

你可能会有疑问,ClassreflectionData属性什么时候被赋值的,其实是封装在Atomic.casReflectionData这个方法里了,他会检测当前Class对象中的reflectionData是否与oldReflectionData相等,如果相等,就会把new SoftReference<>(rd)赋值给reflectionData

到现在为止,关于ReflectionData的背景知识都介绍完了。我们再回到privateGetDeclaredConstructors中看看获取构造方法的流程。

Java 리플렉션 속도에 대해 이야기해 보세요.

privateGetDeclaredConstructors流程图

可以看到对于普通类,最终通过调用getDeclaredConstructors0方法获取的构造方法列表。

private native Constructor[] getDeclaredConstructors0(boolean publicOnly);复制代码
로그인 후 복사
로그인 후 복사

这个方法是 native 的,具体逻辑在 jdk 源码中。

native/java/lang/Class_getDeclaredConstructors0.c文件中,

void getDeclaredConstructors0(Frame * frame){ // Frame 可以理解为调用native方法时,java层传递过来的数据的一种封装 LocalVars * vars = frame->localVars; Object * classObj = getLocalVarsThis(vars); // 取得java方法的入参 bool publicOnly = getLocalVarsBoolean(vars, 1); uint16_t constructorsCount = 0; // 获取要查询的类的 Class 对象 Class * c = classObj->extra; // 获取这个类的所有构造方法,且数量保存在 constructorsCount 中 Method* * constructors = getClassConstructors(c, publicOnly, &constructorsCount); // 获取 java 方法调用所属的 classLoader ClassLoader * classLoader = frame->method->classMember.attachClass->classLoader; // 拿到 Constructor 对应的 class 对象 Class * constructorClass = loadClass(classLoader, "java/lang/reflect/Constructor"); //创建一个长度为 constructorsCount 的数组保存构造方法 Object * constructorArr = newArray(arrayClass(constructorClass), constructorsCount); pushOperandRef(frame->operandStack, constructorArr); // 后面是具体的赋值逻辑。将native中的Method对象转化为java层的Constructor对象 if (constructorsCount > 0) { Thread * thread = frame->thread; Object* * constructorObjs = getObjectRefs(constructorArr); Method * constructorInitMethod = getClassConstructor(constructorClass, _constructorConstructorDescriptor); for (uint16_t i = 0; i < constructorsCount; i++) { Method * constructor = constructors[i]; Object * constructorObj = newObject(constructorClass); constructorObj->extra = constructor; constructorObjs[i] = constructorObj; OperandStack * ops = newOperandStack(9); pushOperandRef(ops, constructorObj); pushOperandRef(ops, classObj); pushOperandRef(ops, toClassArr(classLoader, methodParameterTypes(constructor), constructor->parsedDescriptor->parameterTypesCount)); if (constructor->exceptions != NULL) pushOperandRef(ops, toClassArr(classLoader, methodExceptionTypes(constructor), constructor->exceptions->number_of_exceptions)); else pushOperandRef(ops, toClassArr(classLoader, methodExceptionTypes(constructor), 0)); pushOperandInt(ops, constructor->classMember.accessFlags); pushOperandInt(ops, 0); pushOperandRef(ops, getSignatureStr(classLoader, constructor->classMember.signature)); // signature pushOperandRef(ops, toByteArr(classLoader, constructor->classMember.annotationData, constructor->classMember.annotationDataLen)); pushOperandRef(ops, toByteArr(classLoader, constructor->parameterAnnotationData, constructor->parameterAnnotationDataLen)); Frame * shimFrame = newShimFrame(thread, ops); pushThreadFrame(thread, shimFrame); // init constructorObj InvokeMethod(shimFrame, constructorInitMethod); } } }复制代码
로그인 후 복사
로그인 후 복사

从上面的逻辑,可以知道获取构造方法的核心方法是getClassConstructors,所在文件为rtda/heap/class.c

Method* * getClassConstructors(Class * self, bool publicOnly, uint16_t * constructorsCount){ // 分配大小为 sizeof(Method) 的长度为 methodsCount 的连续内存地址,即数组 Method* * constructors = calloc(self->methodsCount, sizeof(Method)); *constructorsCount = 0; // 在native 层,构造方法和普通方法都存在 methods 中,逐一遍历 for (uint16_t i = 0; i < self->methodsCount; i++) { Method * method = self->methods + i; // 判断是否是构造方法 if (isMethodConstructor(method)) { // 检查权限 if (!publicOnly || isMethodPublic(method)) { // 符合条件的构造方法依次存到数组中 constructors[*constructorsCount] = method; (*constructorsCount)++; } } } return constructors; }复制代码
로그인 후 복사
로그인 후 복사

可以看到getClassConstructors实际上就是对methods进行了一次过滤,过滤的条件为:1.是构造方法;2.权限一致。

isMethodConstructor方法的判断逻辑也是十分简单,不是静态方法,而且方法名是即可。

bool isMethodConstructor(Method * self){ return !isMethodStatic(self) && strcmp(self->classMember.name, "") == 0; }复制代码
로그인 후 복사
로그인 후 복사

所以核心的逻辑变成了Class中的methods数组何时被初始化赋值的?我们刨根问底的追踪下。

我们先找到类加载到虚拟机中的入口方法loadNonArrayClass

Class * loadNonArrayClass(ClassLoader * classLoader, const char * className){ int32_t classSize = 0; char * classContent = NULL; Class * loadClass = NULL; classSize = readClass(className, &classContent); if (classSize > 0 && classContent != NULL){#if 0 printf("class size:%d,class data:[", classSize); for (int32_t i = 0; i < classSize; i++) { printf("0x%02x ", classContent[i]); } printf("]\n");#endif } if (classSize <= 0) { printf("Could not found target class\n"); exit(127); } // 解析字节码文件 loadClass = parseClassFile(classContent, classSize); loadClass->classLoader = classLoader; // 加载 defineClass(classLoader, loadClass); // 链接 linkClass(classLoader, loadClass); //printf("[Loaded %s\n", loadClass->name); return loadClass; }复制代码
로그인 후 복사
로그인 후 복사

parseClassFile方法中,调用了newClass方法。

Class * parseClassFile(char * classContent, int32_t classSize){ ClassFile * classFile = NULL; classFile = parseClassData(classContent, classSize); return newClass(classFile); }复制代码
로그인 후 복사
로그인 후 복사

newClass方法在rtda/heap/class.c文件中。

Class * newClass(ClassFile * classFile){ Class * c = calloc(1, sizeof(Class)); c->accessFlags = classFile->accessFlags; c->sourceFile = getClassSourceFileName(classFile); newClassName(c, classFile); newSuperClassName(c, classFile); newInterfacesName(c, classFile); newConstantPool(c, classFile); newFields(c, classFile); newMethods(c, classFile); return c; }复制代码
로그인 후 복사

可以看到,在native层创建了一个Class对象,我们重点看newMethods(c, classFile)方法啊,这个方法定义在rtda/heap/method.c中。

Method * newMethods(struct Class * c, ClassFile * classFile){ c->methodsCount = classFile->methodsCount; c->methods = NULL; if (c->methodsCount == 0) return NULL; c->methods = calloc(classFile->methodsCount, sizeof(Method)); for (uint16_t i = 0; i < c->methodsCount; i++) { c->methods[i].classMember.attachClass = c; copyMethodInfo(&c->methods[i], &classFile->methods[i], classFile); copyAttributes(&c->methods[i], &classFile->methods[i], classFile); MethodDescriptor * md = parseMethodDescriptor(c->methods[i].classMember.descriptor); c->methods[i].parsedDescriptor = md; calcArgSlotCount(&c->methods[i]); if (isMethodNative(&c->methods[i])) { injectCodeAttribute(&c->methods[i], md->returnType); } } return NULL; }复制代码
로그인 후 복사

上述代码可以看出,实际上就是把ClassFile中解析到的方法逐一赋值给了Class对象的methods数组。

总算梳理清楚了,反射创建对象的调用链为:

loadClass -> loadNonArrayClass -> parseClassFile -> newMethods -> Class 的 methods数组 privateGetDeclaredConstructors -> getDeclaredConstructors0 -> getClassConstructors (过滤Class 的 methods数组)复制代码
로그인 후 복사

到目前为止,我们搞明白反射时如何找到对应的构造方法的。下面我们来看newInstance方法。

(InnerClass) clazz.getDeclaredConstructor().newInstance();复制代码
로그인 후 복사
public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { // 构造方法是否被重载了 if (!override) { if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) { Class caller = Reflection.getCallerClass(); // 检查权限 checkAccess(caller, clazz, null, modifiers); } } // 枚举类型报错 if ((clazz.getModifiers() & Modifier.ENUM) != 0) throw new IllegalArgumentException("Cannot reflectively create enum objects"); // ConstructorAccessor 是缓存的,如果为空,就去创建一个 ConstructorAccessor ca = constructorAccessor; // read volatile if (ca == null) { // 创建 ConstructorAccessor ca = acquireConstructorAccessor(); } @SuppressWarnings("unchecked") // 使用 ConstructorAccessor 的 newInstance 构造实例 T inst = (T) ca.newInstance(initargs); return inst; }复制代码
로그인 후 복사

接着看下acquireConstructorAccessor方法。

private ConstructorAccessor acquireConstructorAccessor() { // First check to see if one has been created yet, and take it // if so. ConstructorAccessor tmp = null; // 可以理解为缓存的对象 if (root != null) tmp = root.getConstructorAccessor(); if (tmp != null) { constructorAccessor = tmp; } else { // Otherwise fabricate one and propagate it up to the root // 生成一个 ConstructorAccessor,并缓存起来 tmp = reflectionFactory.newConstructorAccessor(this); setConstructorAccessor(tmp); } return tmp; }复制代码
로그인 후 복사

继续走到newConstructorAccessor方法。

public ConstructorAccessor newConstructorAccessor(Constructor var1) { checkInitted(); Class var2 = var1.getDeclaringClass(); // 如果是抽象类,报错 if (Modifier.isAbstract(var2.getModifiers())) { return new InstantiationExceptionConstructorAccessorImpl((String)null); } // 如果 Class 类报错 else if (var2 == Class.class) { return new InstantiationExceptionConstructorAccessorImpl("Can not instantiate java.lang.Class"); } // 如果是 ConstructorAccessorImpl 的子类的话,返回 BootstrapConstructorAccessorImpl else if (Reflection.isSubclassOf(var2, ConstructorAccessorImpl.class)) { return new BootstrapConstructorAccessorImpl(var1); } // 判断 noInflation , 后面是判断不是匿名类 else if (noInflation && !ReflectUtil.isVMAnonymousClass(var1.getDeclaringClass())) { return (new MethodAccessorGenerator()).generateConstructor(var1.getDeclaringClass(), var1.getParameterTypes(), var1.getExceptionTypes(), var1.getModifiers()); } // 使用 NativeConstructorAccessorImpl 来生成实例 else { NativeConstructorAccessorImpl var3 = new NativeConstructorAccessorImpl(var1); DelegatingConstructorAccessorImpl var4 = new DelegatingConstructorAccessorImpl(var3); var3.setParent(var4); return var4; } }复制代码
로그인 후 복사

具体逻辑,在上述代码中已经注释了。这里提一下noInflation

ReflectionFactory在执行所有方法前会检查下是否执行过了checkInitted方法,这个方法会把noInflation的值和inflationThreshold从虚拟机的环境变量中读取出来并赋值。

noInflationfalse而且不是匿名类时,就会使用MethodAccessorGenerator方式。否则就是用NativeConstructorAccessorImpl的方式来生成。

默认noInflationfalse,所以我们先看native调用的方式。关注NativeConstructorAccessorImpl类。

class NativeConstructorAccessorImpl extends ConstructorAccessorImpl { private final Constructor c; private DelegatingConstructorAccessorImpl parent; private int numInvocations; NativeConstructorAccessorImpl(Constructor var1) { this.c = var1; } public Object newInstance(Object[] var1) throws InstantiationException, IllegalArgumentException, InvocationTargetException { if (++this.numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(this.c.getDeclaringClass())) { ConstructorAccessorImpl var2 = (ConstructorAccessorImpl)(new MethodAccessorGenerator()).generateConstructor(this.c.getDeclaringClass(), this.c.getParameterTypes(), this.c.getExceptionTypes(), this.c.getModifiers()); this.parent.setDelegate(var2); } return newInstance0(this.c, var1); } void setParent(DelegatingConstructorAccessorImpl var1) { this.parent = var1; } private static native Object newInstance0(Constructor var0, Object[] var1) throws InstantiationException, IllegalArgumentException, InvocationTargetException; }复制代码
로그인 후 복사

我们可以看到NativeConstructorAccessorImpl中维护了一个计数器numInvocations,在每次调用newInstance方法生成实例时,就会对计数器自增,当计数器超过ReflectionFactory.inflationThreshold()的阈值,默认为15,就会使用ConstructorAccessorImpl替换NativeConstructorAccessorImpl,后面就会直接调用MethodAccessorGenerator中的方法了。

我们先看看没到达阈值前,会调用native方法newInstance0,这个方法定义在native/sun/reflect/NativeConstructorAccessorImpl.c中,具体newInstance0的流程我就不分析了,大致逻辑是操作堆栈执行方法。

然后我们再看看超过阈值后,执行的是MethodAccessorGenerator生成构造器的方式。这种方式与newConstructorAccessor方法中noInflationfalse的处理方式一样。所以可以解释为:java虚拟机在执行反射操作时,如果同一操作执行次数超过阈值,会从native生成实例的方式转变为java生成实例的方式。

MethodAccessorGeneratorMethodAccessorGenerator方法如下。

public ConstructorAccessor generateConstructor(Class var1, Class[] var2, Class[] var3, int var4) { return (ConstructorAccessor)this.generate(var1, "", var2, Void.TYPE, var3, var4, true, false, (Class)null); }复制代码
로그인 후 복사

继续跟踪下去可以发现,反射调用构造方法实际上是动态编写字节码,并且在虚拟机中把编好的字节码加载成一个Class,这个Class实际上是ConstructorAccessorImpl类型的,然后调用这个动态类的newInstance方法。回看刚刚我们梳理的newConstructorAccessor代码,可以看到第三个逻辑:

// 如果是 ConstructorAccessorImpl 的子类的话,返回 BootstrapConstructorAccessorImpl else if (Reflection.isSubclassOf(var2, ConstructorAccessorImpl.class)) { return new BootstrapConstructorAccessorImpl(var1); } 复制代码
로그인 후 복사

最终执行的是BootstrapConstructorAccessorImplnewInstance方法。

class BootstrapConstructorAccessorImpl extends ConstructorAccessorImpl { private final Constructor constructor; BootstrapConstructorAccessorImpl(Constructor var1) { this.constructor = var1; } public Object newInstance(Object[] var1) throws IllegalArgumentException, InvocationTargetException { try { return UnsafeFieldAccessorImpl.unsafe.allocateInstance(this.constructor.getDeclaringClass()); } catch (InstantiationException var3) { throw new InvocationTargetException(var3); } } }复制代码
로그인 후 복사

最后是通过使用Unsafe类分配了一个实例。

反射带来的问题

到现在为止,我们已经把反射生成实例的所有流程都搞清楚了。回到文章开头的问题,我们现在反思下,反射性能低么?为什么?

  1. 反射调用过程中会产生大量的临时对象,这些对象会占用内存,可能会导致频繁 gc,从而影响性能。
  2. 反射调用方法时会从方法数组中遍历查找,并且会检查可见性等操作会耗时。
  3. 反射在达到一定次数时,会动态编写字节码并加载到内存中,这个字节码没有经过编译器优化,也不能享受JIT优化。
  4. 反射一般会涉及自动装箱/拆箱和类型转换,都会带来一定的资源开销。

在Android中,我们可以在某些情况下对反射进行优化。举个例子,EventBus 2.x 会在 register 方法运行时,遍历所有方法找到回调方法;而EventBus 3.x 则在编译期间,将所有回调方法的信息保存的自己定义的SubscriberMethodInfo中,这样可以减少对运行时的性能影响。

想了解更多相关学习,敬请关注php培训栏目!

위 내용은 Java 리플렉션 속도에 대해 이야기해 보세요.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:juejin.im
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿
회사 소개 부인 성명 Sitemap
PHP 중국어 웹사이트:공공복지 온라인 PHP 교육,PHP 학습자의 빠른 성장을 도와주세요!