JDK 如何提升反射方法调用性能

335 阅读4分钟

image.png

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() {
    }
}

image.png

运行结果显示反射调用比正常调用慢了6倍, 接下来我们改变一个运行时属性配置 sun.reflect.inflationThreshold 再次测试. 我们将 sun.reflect.inflationThreshold 值改为 1000000 和循环次数一致。

image.png

image.png

改动 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);
}

image.png

NativeMethodAccessorImpl 就是我们在控制台输出中看的,它的 invoke 方法实现是调用了 native 方法。再通过对 .class 反编译的方式我们得到 sun.reflect.GeneratedMethodAccessor1sun.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);
}

NativeMethodAccessorImplinvoke 方法中,会进行一次 if 判断,目的就是动态的去生成 GeneratedMethodAccessor1 这种正常方式去调用目标方法的 MethodAccessor 实现类型。ReflectionFactory.inflationThreshold() 的默认值为 15 , 但是可以通过 sun.reflect.inflationThreshold 属性变量进行指定。这也就是为什么在开篇的性能测试中改变了 sun.reflect.inflationThreshold 属性的值后反射调用的性能大幅度降低,因为它在调用时没有通过动态生成的 MethodAccessor 进行调用而是一直在使用 NativeMethodAccessorImpl .

image.png

image.png


今天是 2023年2月5号 是中国传统节日 元宵节, 最后祝愿所有读者节日快乐。DevX 会持续分享 Java 技术干货,如果你觉得本文对你有帮助希望你可以分享给更多的朋友看到。该文章会同步在微信公众号 【DevXJava】, 方便在微信客户端阅读。

DevXJava 不止于技术