美文网首页JVM
JVM的反射调用实现

JVM的反射调用实现

作者: 麦香小瑜儿 | 来源:发表于2019-02-19 00:07 被阅读0次

如何执行反射调用

Java的反射调用是通过java.lang.reflect.Method的invoke调用执行,Method实例通过反射执行的方法的类的Class实例提供的方法获取,如下:

package com.test;
import java.lang.reflect.Method;
public class ReflectTest {
    public void method1(int arg) {
        new RuntimeException("xxxxxxxx").printStackTrace();;
    }
    public static void main(String[] args) throws Exception {
                //获取需要反射执行的方法的Method的实例
        Method method = ReflectTest.class.getMethod("method1", int.class); 
                //执行method的invoke方法进行反射调用,需要传入反射方法的定义的实例,以及方法的全部入参(按照参数定义的顺序)
        method.invoke(new ReflectTest(), 128);
    }
}
输出:
java.lang.RuntimeException: xxxxxxxx
    at com.test.ReflectTest.method1(ReflectTest.java:8)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.test.ReflectTest.main(ReflectTest.java:13)

从反射调用的异常调用栈可以得知,Method实例的反射执行经过了sun.reflect.NativeMethodAccessorImpl的调用,但这只是通用的情况,下面会具体分析基于Method反射执行的实现原理。

Method反射调用的原理

查阅 Method.invoke(jdk1.8.0) 的源代码,

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

Method.invoke实际上委派给 MethodAccessor来处理。MethodAccessor 是一个接口,它有两个已有的具体实现:一个通过native code来实现反射调用另一个则使用了java本身。在第一次调用一个实际Java方法对应得Method对象的invoke()方法之前,实现调用逻辑的MethodAccessor对象还没创建;等第一次调用时才新创建MethodAccessor并更新给root,然后调用MethodAccessor.invoke()真正完成反射调用。
委派的MethodAccessor类创建过程关键代码如下:

if (noInflation) {  
            return new MethodAccessorGenerator().  
                generateMethod(method.getDeclaringClass(),  
                               method.getName(),  
                               method.getParameterTypes(),  
                               method.getReturnType(),  
                               method.getExceptionTypes(),  
                               method.getModifiers());  
} else {  
            NativeMethodAccessorImpl acc =  
                new NativeMethodAccessorImpl(method);  
            DelegatingMethodAccessorImpl res =  
                new DelegatingMethodAccessorImpl(acc);  
            acc.setParent(res);  
            return res;  
}

如果noInflation标志位为false,将会创建DelegatingMethodAccessorImpl实现类,DelegatingMethodAccessorImpl又是代理了NativeMethodAccessorImpl(native code实现的MethodAccessor)。DelegatingMethodAccessorImpl:

class DelegatingMethodAccessorImpl extends MethodAccessorImpl {  
    private MethodAccessorImpl delegate;  
  
    DelegatingMethodAccessorImpl(MethodAccessorImpl delegate) {  
        setDelegate(delegate);  
    }      
  
    public Object invoke(Object obj, Object[] args)  
        throws IllegalArgumentException, InvocationTargetException  
    {  
        return delegate.invoke(obj, args);  
    }  
  
    void setDelegate(MethodAccessorImpl delegate) {  
        this.delegate = delegate;  
    }  
}

为了权衡两个版本的性能,Sun的JDK使用了“inflation”的技巧:让Java方法在被反射调用时,开头若干次使用native版,等反射调用次数超过阈值时则生成一个专用的MethodAccessor实现类,生成其中的invoke()方法的字节码,以后对该Java方法的反射调用就会使用Java版。
设置切换MethodAccessor的实现类的阀值参数为:-Dsun.reflect.inflationThreshold 。反射调用的 Inflation 机制是可以通过参数(-Dsun.reflect.noInflation=true)来关闭的。这样一来,在反射调用一开始便会直接生成Java实现的MethodAccessor。
Java版本的MethodAccessor实现大致如下(基于开头的com.test.ReflectTest类版本):

package sun.reflect;  
  
public class GeneratedMethodAccessor1 extends MethodAccessorImpl {      
    public GeneratedMethodAccessor1() {  
        super();  
    }  
      
    public Object invoke(Object obj, Object[] args)     
        throws IllegalArgumentException, InvocationTargetException {  
        // prepare the target and parameters  
        if (obj == null) throw new NullPointerException();  
        try {  
            com.test.ReflectTest target = (com.test.ReflectTest) obj;  
            if (args.length != 1) throw new IllegalArgumentException();  
            int arg0 = (int) args[0];  
        } catch (ClassCastException e) {  
            throw new IllegalArgumentException(e.toString());  
        } catch (NullPointerException e) {  
            throw new IllegalArgumentException(e.toString());  
        }  
        // make the invocation  
        try {  
            target.method1(arg0);  
        } catch (Throwable t) {  
            throw new InvocationTargetException(t);  
        }  
    }  
}  

可见Java版本的MethodAccessor实现,其反射调用可以认为是对目标方法的invokevirtual调用。当该反射调用成为热点时,它甚至可以被内联到靠近Method.invoke()的一侧,大大降低了反射调用的开销。而native版的反射调用则无法被有效内联,因而调用开销无法随程序的运行而降低。

两种实现的性能比较

  • Java实现的版本在初始化时需要较多时间,但是执行性能较好
  • native版本正好相反,启动时相对较快
  • native版本将会阻碍JVM的优化(跨越native边界会对优化有阻碍作用,它就像个黑箱一样让虚拟机难以分析也将其内联)

性能优化

Java版本的MethodAccessor实现的反射调用的性能之所以得到优化,主要是因为即时编译器中的方法内联。在关闭了 Inflation 的情况下,内联的瓶颈在于 Method.invoke 方法中对 MethodAccessor.invoke 方法的调用。由于 Java 虚拟机的关于上述调用点的类型 profile(注:对于 invokevirtual 或者 invokeinterface,Java 虚拟机会记录下调用者的具体类型,我们称之为类型 profile)无法同时记录这么多个类,因此可能造成所测试的反射调用没有被内联的情况。
下面比较因为调用点的类型profile超出阀值导致JVM无法内联优化热点反射调用的性能损耗,先是看看能够内联优化的情况:

public static void main(String[] args) throws Exception {
                Method method = ReflectTest.class.getMethod("method1", int.class);
//      polluteProfile();
        ReflectTest obj = new ReflectTest();
        long cur = System.currentTimeMillis();
        for(int i=0;i<2000000000;i++) {
            if(i % 100000000 == 0) {
                long tmp = System.currentTimeMillis();
                System.out.println("cost=" + (tmp - cur));
                cur = tmp;
        }
        method.invoke(obj, 128);    
}
最后五次耗时统计输出:
cost=980
cost=1150
cost=1132
cost=1341
cost=1417       

以下是通过创建被反射的类多个方法的Method对象并存的场景,扰乱JVM的热点内联优化:

//扰乱内联优化的方法
    private static void polluteProfile() throws Exception {
        Method method1 = ReflectTest.class.getMethod("method2", int.class);
        Method method2 = ReflectTest.class.getMethod("method3", int.class);
        ReflectTest obj = new ReflectTest();
        for(int i=0;i<2000;i++) {
            method1.invoke(obj, 1);
            method1.invoke(obj, 2);
        }
    }
    
    public static void main(String[] args) throws Exception {
        Method method = ReflectTest.class.getMethod("method1", int.class);
        polluteProfile();
        ReflectTest obj = new ReflectTest();
        long cur = System.currentTimeMillis();
        for(int i=0;i<2000000000;i++) {
            if(i % 100000000 == 0) {
                long tmp = System.currentTimeMillis();
                System.out.println("cost=" + (tmp - cur));
                cur = tmp;
            }
            method.invoke(obj, 128);    
        }
      }
最后五次耗时统计输出:
cost=5545
cost=5521
cost=5488
cost=5536
cost=5488

可以提高 Java 虚拟机关于每个调用能够记录的类型数目(对应虚拟机参数 -XX:TypeProfileWidth,默认值为 2,实测-XX:TypeProfileWidth=3作为JVM参数在本地笔记本无效)。

参考

相关文章

  • JVM的反射调用实现

    如何执行反射调用 Java的反射调用是通过java.lang.reflect.Method的invoke调用执行,...

  • Class对象的一些概念

    最近在写动态代理的demo,反射是动态代理的核心实现,而反射是通过jvm中的class对象在运行期动态调用任意一个...

  • JVM的反射实现

    java的反射机制 java的反射机制是在运行状态中,对于任意一个类(Class)都能知道他的属性(Field)和...

  • 【Java 进阶】Java 反射

    反射:获取Class中所有字段(Field)与方法(Method),并实现调用(invoke) Java 反射简单...

  • Java使用invoke反射调用方法导致@Value、@Auto

    发生背景:开发过程中使用到invoke进行反射调用serviceImpl实现类的方法,在运行中发现采用反射方式调用...

  • java反射为何会慢

    反射是动态的对类型、方法进行解析,肯定是会比直接调用慢一点。jvm无法进行优化

  • 深入理解Java虚拟机三

    一、Java反射的实现原理 1.反射调用的实现 反射是Java语言中一个相当重要的特性,它允许正在运行的Ja...

  • Java反射在JVM的实现

    The implementation of Java reflection in JVM 本文目录 什么是Java...

  • go 的反射 reflect

    Golang语言实现了反射,反射机制就是在运行时动态的调用对象的方法和属性,官方自带的reflect包就是反射相关...

  • Java高级-反射

    15.1.Java反射机制概述 15.2.理解Class类并获取Class实例(重点) 用反射实现类的实例化,调用...

网友评论

    本文标题:JVM的反射调用实现

    本文链接:https://www.haomeiwen.com/subject/xhytyqtx.html