JDK 如何提升反射方法调用性能
所有的
java
程序员在初学时都会被告知反射调用方法比正常调用方法要慢性能会更差。事关性能问题一般都不是小问题,本章我们就来探究jdk
层面是如何优化这个问题的。
性能对比
public class ReflectionMethodPerformanceTest {
public static void main(String[] args) throws Exception {
// -Dsun.reflect.inflationThreshold=1000000
ReflectionMethodPerformanceTest performanceTest = new ReflectionMethodPerformanceTest();
int loop = 1000000;
long s1 = System.currentTimeMillis();
for (int i = 0 ; i < loop ; i++) {
performanceTest.foo();
}
System.out.println(String.format("正常调用 [%s] 次耗时 [%s] 毫秒" , loop , System.currentTimeMillis() - s1));
Method method = ReflectionMethodPerformanceTest.class.getMethod("foo");
long s2 = System.currentTimeMillis();
for (int i = 0 ; i < loop ; i++) {
method.invoke(performanceTest);
}
System.out.println(String.format("反射调用 [%s] 次耗时 [%s] 毫秒" , loop , System.currentTimeMillis() - s2));
}
public void foo() {
}
}
运行结果显示反射调用比正常调用慢了6倍, 接下来我们改变一个运行时属性配置 sun.reflect.inflationThreshold
再次测试. 我们将 sun.reflect.inflationThreshold
值改为 1000000 和循环次数一致。
改动 sun.reflect.inflationThreshold
属性后再测试发现反射调用方法性能变得更差了,比未改动时差了 8 倍。接下来我们就来探究一下这是为什么。
场景
在场景代码中我们反射调用了两个方法一个静态一个非静态,观察日志输入,以及
Method
中的methodAccessor
属性。
public class TestMethodInvoke {
public static void main(String[] args) throws Exception {
// -Dsun.reflect.inflationThreshold=5
TestMethodInvoke methodInvoke = new TestMethodInvoke();
// jdk 反射调用优化
Method fooMethod = TestMethodInvoke.class.getDeclaredMethod("foo" , int.class);
Method foo1Method = TestMethodInvoke.class.getDeclaredMethod("foo1" , int.class);
for (int i = 0 ; i <= 16 ; i++) {
show(i , fooMethod);
fooMethod.invoke(null , i);
show(i , foo1Method);
foo1Method.invoke(methodInvoke , i);
}
System.in.read();
}
private static void show(int i , Method method) throws Exception {
Field methodAccessorField = Method.class.getDeclaredField("methodAccessor");
methodAccessorField.setAccessible(true);
Object methodAccessor = methodAccessorField.get(method);
if (Objects.nonNull(methodAccessor)) {
Field delegateField = methodAccessor.getClass().getDeclaredField("delegate");
delegateField.setAccessible(true);
methodAccessor = delegateField.get(methodAccessor);
}
System.out.println(method.getName() + " -> " + i + " : " + methodAccessor);
}
public static void foo(int i) {
System.out.println(i + " : foo");
}
public static void foo1(int i) {
System.out.println(i + " : foo1");
}
}
控制台输出结果
foo -> 0 : null
0 : foo
foo1 -> 0 : null
0 : foo1
foo -> 1 : sun.reflect.NativeMethodAccessorImpl@24273305
1 : foo
foo1 -> 1 : sun.reflect.NativeMethodAccessorImpl@5b1d2887
1 : foo1
foo -> 2 : sun.reflect.NativeMethodAccessorImpl@24273305
2 : foo
foo1 -> 2 : sun.reflect.NativeMethodAccessorImpl@5b1d2887
2 : foo1
foo -> 3 : sun.reflect.NativeMethodAccessorImpl@24273305
3 : foo
foo1 -> 3 : sun.reflect.NativeMethodAccessorImpl@5b1d2887
3 : foo1
foo -> 4 : sun.reflect.NativeMethodAccessorImpl@24273305
4 : foo
foo1 -> 4 : sun.reflect.NativeMethodAccessorImpl@5b1d2887
4 : foo1
foo -> 5 : sun.reflect.NativeMethodAccessorImpl@24273305
5 : foo
foo1 -> 5 : sun.reflect.NativeMethodAccessorImpl@5b1d2887
5 : foo1
foo -> 6 : sun.reflect.NativeMethodAccessorImpl@24273305
6 : foo
foo1 -> 6 : sun.reflect.NativeMethodAccessorImpl@5b1d2887
6 : foo1
foo -> 7 : sun.reflect.NativeMethodAccessorImpl@24273305
7 : foo
foo1 -> 7 : sun.reflect.NativeMethodAccessorImpl@5b1d2887
7 : foo1
foo -> 8 : sun.reflect.NativeMethodAccessorImpl@24273305
8 : foo
foo1 -> 8 : sun.reflect.NativeMethodAccessorImpl@5b1d2887
8 : foo1
foo -> 9 : sun.reflect.NativeMethodAccessorImpl@24273305
9 : foo
foo1 -> 9 : sun.reflect.NativeMethodAccessorImpl@5b1d2887
9 : foo1
foo -> 10 : sun.reflect.NativeMethodAccessorImpl@24273305
10 : foo
foo1 -> 10 : sun.reflect.NativeMethodAccessorImpl@5b1d2887
10 : foo1
foo -> 11 : sun.reflect.NativeMethodAccessorImpl@24273305
11 : foo
foo1 -> 11 : sun.reflect.NativeMethodAccessorImpl@5b1d2887
11 : foo1
foo -> 12 : sun.reflect.NativeMethodAccessorImpl@24273305
12 : foo
foo1 -> 12 : sun.reflect.NativeMethodAccessorImpl@5b1d2887
12 : foo1
foo -> 13 : sun.reflect.NativeMethodAccessorImpl@24273305
13 : foo
foo1 -> 13 : sun.reflect.NativeMethodAccessorImpl@5b1d2887
13 : foo1
foo -> 14 : sun.reflect.NativeMethodAccessorImpl@24273305
14 : foo
foo1 -> 14 : sun.reflect.NativeMethodAccessorImpl@5b1d2887
14 : foo1
foo -> 15 : sun.reflect.NativeMethodAccessorImpl@24273305
15 : foo
foo1 -> 15 : sun.reflect.NativeMethodAccessorImpl@5b1d2887
15 : foo1
foo -> 16 : sun.reflect.GeneratedMethodAccessor1@67117f44
16 : foo
foo1 -> 16 : sun.reflect.GeneratedMethodAccessor2@5d3411d
16 : foo1
通过观察输出结果发现无论是静态方法还是非静态方法前15次调用
methodAccessor
类型都是sun.reflect.NativeMethodAccessorImpl
而在第16次调用时foo
方法的methodAccessor
类型变成了sun.reflect.GeneratedMethodAccessor1
,foo1
方法的methodAccessor
类型变成了sun.reflect.GeneratedMethodAccessor2
. 并且在jdk
的源码中搜索不到这两个类.
先来观察下 jdk java.lang.reflect.Method#invoke 的源码。所有的反射调用实际上是通过
MethodAccessor
完成的。
@CallerSensitive
public 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);
}
NativeMethodAccessorImpl
就是我们在控制台输出中看的,它的 invoke
方法实现是调用了 native
方法。再通过对 .class
反编译的方式我们得到 sun.reflect.GeneratedMethodAccessor1
和 sun.reflect.GeneratedMethodAccessor2
类型的源代码这两个类型是动态生成的。
GeneratedMethodAccessor1
/*
* Decompiled with CFR.
*
* Could not load the following classes:
* org.devx.spring.certified.professional.edu.course.a12.TestMethodInvoke
*/
package sun.reflect;
import java.lang.reflect.InvocationTargetException;
import org.devx.spring.certified.professional.edu.course.a12.TestMethodInvoke;
import sun.reflect.MethodAccessorImpl;
public class GeneratedMethodAccessor1
extends MethodAccessorImpl {
/*
* Loose catch block
*/
public Object invoke(Object object, Object[] objectArray) throws InvocationTargetException {
char c;
block9: {
if (objectArray.length != 1) {
throw new IllegalArgumentException();
}
Object object2 = objectArray[0];
if (object2 instanceof Byte) {
c = ((Byte)object2).byteValue();
break block9;
}
if (object2 instanceof Character) {
c = ((Character)object2).charValue();
break block9;
}
if (object2 instanceof Short) {
c = (char)((Short)object2).shortValue();
break block9;
}
if (object2 instanceof Integer) {
c = (char)((Integer)object2).intValue();
break block9;
}
throw new IllegalArgumentException();
}
try {
TestMethodInvoke.foo((int)c);
return null;
}
catch (Throwable throwable) {
throw new InvocationTargetException(throwable);
}
catch (ClassCastException | NullPointerException runtimeException) {
throw new IllegalArgumentException(super.toString());
}
}
}
GeneratedMethodAccessor2
/*
* Decompiled with CFR.
*
* Could not load the following classes:
* org.devx.spring.certified.professional.edu.course.a12.TestMethodInvoke
*/
package sun.reflect;
import java.lang.reflect.InvocationTargetException;
import org.devx.spring.certified.professional.edu.course.a12.TestMethodInvoke;
import sun.reflect.MethodAccessorImpl;
public class GeneratedMethodAccessor2
extends MethodAccessorImpl {
/*
* Loose catch block
*/
public Object invoke(Object object, Object[] objectArray) throws InvocationTargetException {
char c;
TestMethodInvoke testMethodInvoke;
block10: {
if (object == null) {
throw new NullPointerException();
}
testMethodInvoke = (TestMethodInvoke)object;
if (objectArray.length != 1) {
throw new IllegalArgumentException();
}
Object object2 = objectArray[0];
if (object2 instanceof Byte) {
c = ((Byte)object2).byteValue();
break block10;
}
if (object2 instanceof Character) {
c = ((Character)object2).charValue();
break block10;
}
if (object2 instanceof Short) {
c = (char)((Short)object2).shortValue();
break block10;
}
if (object2 instanceof Integer) {
c = (char)((Integer)object2).intValue();
break block10;
}
throw new IllegalArgumentException();
}
try {
testMethodInvoke.foo1((int)c);
return null;
}
catch (Throwable throwable) {
throw new InvocationTargetException(throwable);
}
catch (ClassCastException | NullPointerException runtimeException) {
throw new IllegalArgumentException(super.toString());
}
}
}
通过观察上面两个反编译过来的源码不难发现,方法的调用最终都被转换成了正常方式调用. 接下来我们再来看看 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);
}
在 NativeMethodAccessorImpl
的 invoke
方法中,会进行一次 if
判断,目的就是动态的去生成 GeneratedMethodAccessor1
这种正常方式去调用目标方法的 MethodAccessor
实现类型。ReflectionFactory.inflationThreshold()
的默认值为 15 , 但是可以通过 sun.reflect.inflationThreshold
属性变量进行指定。这也就是为什么在开篇的性能测试中改变了 sun.reflect.inflationThreshold
属性的值后反射调用的性能大幅度降低,因为它在调用时没有通过动态生成的 MethodAccessor
进行调用而是一直在使用 NativeMethodAccessorImpl
.
今天是 2023年2月5号
是中国传统节日 元宵节
, 最后祝愿所有读者节日快乐。DevX
会持续分享 Java
技术干货,如果你觉得本文对你有帮助希望你可以分享给更多的朋友看到。该文章会同步在微信公众号 【DevXJava】, 方便在微信客户端阅读。