《深入理解JVM》9-如何实现反射

在本系列的第8节,我们总结了一下jvm怎么执行方法调用,是在类中维护一个方法表,指向的是方法的实际内存地址。查找内存地址的执行时间不通,可以划分为静态绑定和动态绑定。那么反射是怎么执行的呢

    public void invokeTest() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {

        Class deClazs =  Demo.class;
        Method sayHel =  deClazs.getMethod("sayHello", int.class);
        sayHel.setAccessible(true);
        sayHel.invoke(null,1);

    }

    static class Demo{
        static void sayHello(int i){
            System.out.println(i);
        }
    }

上面的代码是简单的反射调用,查看反射代码

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

性能消耗:

  1. Class.getMethod()
  2. invoke自动组装变长参数Object..args
  3. int 类型的拆箱装箱
  4. MethodAccessor的绑定

发现是调用的MethodAccessor 来执行的。这个地方靠的是委派机制,因为MethodAccessor有两个实现

  1. NativeMethodAccessorImpl:本地执行方法,是c语言实现,执行较慢,需要经过java-》C-》java的切换,
  2. DelegatingMethodAccessorImpl:动态代理实现,生成字节码,直接调用invoke方法,执行较快,但字节码生成速度较慢。只有在超过15此的阈值之后才会启动字节码生成和方法替换。

但是动态代理有个问题,因为实际实现方法可能比较多,在MethodAccessor中缓存的方法profile(目标类+方法名称)只有默认的两个,在多个实现的时候,动态代理的内联就不能派上用场,可以调整参数:-XX:TypeProfileWidth 增大缓存的记录

猜你喜欢

转载自blog.csdn.net/David_lou/article/details/109099934
今日推荐