《深入理解Java虚拟机》JVM是如何实现反射的?

1.反射调用的实现

本地实现

package com.sz.simple;

import java.lang.reflect.Method;

public class Test {
    public static void target() {
        new Exception("#").printStackTrace();
    }

    public static void main(String[] args) throws Exception {

        Test test = new Test();

        Class<?> klass = Class.forName("com.sz.simple.Test");
        Method method = klass.getMethod("target");
        method.invoke(test,null);
    }
}

如上代码,当执行main方法时,首先会通过全限定类名获取到该类的字节码对象,然后获取到Method对象后反射调用执行目标方法。打印方法执行时的堆栈轨迹如下图

image-20230512141230970

反射类调用委派类的invoke方法,然后委派类调用本地方法的invoke方法,最后执行目标方法

Java反射方法 ->java委派方法 -> c++本地方法 -> java目标方法

由于 java 到 c++ 方法 再到 java 方法的这一过程非常耗时,因此 委派类除了可以委派给本地方法实现外,还能委派给动态方法实现

动态实现

将上述代码修改为如下所示

package com.sz.simple;

import java.lang.reflect.Method;

public class Test {
    public static void target(int i) {
        new Exception("#"+i).printStackTrace();
    }

    public static void main(String[] args) throws Exception {

        Test test = new Test();

        Class<?> klass = Class.forName("com.sz.simple.Test");

        for (int i = 0; i < 17; i++) {
            Method method = klass.getMethod("target",int.class);
            method.invoke(test,i);
        }

    }
}
java -verbose:class com.sz.simple.Test

image-20230512142314822

对比第15次和第16次打印,你会发现,在第16次执行的时候,委派类委派给了GeneratedMethodAccessor,采用了动态实现。

改为动态实现的 15 临界值我们可用通过修改 Dsun.reflect.inflationThreshold 的值修改,如果你的项目中大量使用到了反射去调用方法,而不想产生大量动态生成类,可用设置 Dsun.reflect.inflationThreshold = int.max

如果你不想使用本地实现去调用目标方法,你可以执行时添加如下的参数

-Dsun.reflect.noInflation=true

2. 两种反射调用的实现效率

动态实现和本地实现相比,其运行效率要快上 20 倍 [2] 。这是因为动态实现无需经过 Java 到 C++ 再到 Java 的切换,但由于生成字节码十分耗时,仅调用一次的话,反而是本地实现要快上 3 到 4 倍 [3]。

猜你喜欢

转载自blog.csdn.net/JAVAlife2021/article/details/130642063