JDK1.8里Method.invoke()的实现原理

简介

上一篇文章[java注解研究]提到注解最终通过生成动态代理实现类方式来调用,而动态代理实际就是通过反射来调用类中的方法,也就是Method.invoke()调用。Method.invoke()用途广泛,像刚才所说的动态代理反射调用,框架经常使用的SPI扩展等。所以本篇有必要了解下Method.invoke()的实现。

例子

import java.lang.reflect.Method;

public class MethodTest {
    public static void main(String[] args) throws Exception {
        Class<?> clz = Class.forName("com.method.TestClassLoad");
        Object o = clz.newInstance();
        Method m = clz.getMethod("test", String.class,Integer.class);        
        for (int i = 0; i < 16; i++) {
            m.invoke(o, "huige",i);
        }
    }

}
class TestClassLoad {
    public void test(String s,Integer i){
        System.out.println(s+":测试:"+i);
    }
}

注意到MethodTest 类上不会有对类TestClassLoad 的符号依赖——也就是说在加载并初始化MethodTest 类时不需要关心类TestClassLoad 的存在与否,而是等到main()方法执行到调用Class.forName()时才试图对类TestClassLoad 做动态加载;这里用的是一个参数版的forName(),也就是使用当前方法所在类的ClassLoader来加载,并且初始化新加载的类

原理

JDK里Method.invoke()是怎么实现的。
java.lang.reflect.Method#invoke

 @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();//获取MethodAccessor
        }
        return ma.invoke(obj, args);
    }

    private Method  root;

    //获取MethodAccessor
    private MethodAccessor acquireMethodAccessor() {
        // First check to see if one has been created yet, and take it
        // if so
        MethodAccessor tmp = null;
        if (root != null) tmp = root.getMethodAccessor();
        if (tmp != null) {
            methodAccessor = tmp;
        } else {
            // Otherwise fabricate one and propagate it up to the root
            tmp = reflectionFactory.newMethodAccessor(this);
            setMethodAccessor(tmp);
        }
        return tmp;
    }
  //把新创建methodAccessor对象通过root包装起来
  void setMethodAccessor(MethodAccessor accessor) {
        methodAccessor = accessor;
        // Propagate up
        if (root != null) {
            root.setMethodAccessor(accessor);
        }
    }

可以看到Method.invoke()实际上并不是自己实现的反射调用逻辑,而是委托给sun.reflect.MethodAccessor来处理。 每个实际的Java方法只有一个对应的Method对象作为root。这个root是不会暴露给用户的,而是每次在通过反射获取Method对象时,把新创建methodAccessor对象通过root包装起来。在第一次调用一个实际Java方法对应的Method对象的invoke()方法之前,实现调用逻辑的MethodAccessor对象还没创建;等第一次调用时才新创建MethodAccessor并更新给root,然后调用MethodAccessor.invoke()真正完成反射调用。

那么MethodAccessor是啥呢?
sun.reflect.MethodAccessor

public interface MethodAccessor {
    Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException;
}

可以看到它只是一个单方法接口,其invoke()方法与Method.invoke()的对应。

创建MethodAccessor实例的是ReflectionFactory
sun.reflect.ReflectionFactory#newMethodAccessor

    private static boolean noInflation = false;
    //调用超过15次就采用java版本
    private static int inflationThreshold = 15;
    private static void checkInitted() {
        if (!initted) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    if (System.out == null) {
                        return null;
                    } else {
                        String var1 = System.getProperty("sun.reflect.noInflation");
                        if (var1 != null && var1.equals("true")) {
                            ReflectionFactory.noInflation = true;
                        }

                        var1 = System.getProperty("sun.reflect.inflationThreshold");
                        if (var1 != null) {
                            try {
                                ReflectionFactory.inflationThreshold = Integer.parseInt(var1);
                            } catch (NumberFormatException var3) {
                                throw new RuntimeException("Unable to parse property sun.reflect.inflationThreshold", var3);
                            }
                        }

                        ReflectionFactory.initted = true;
                        return null;
                    }
                }
            });
        }
    }
    public MethodAccessor newMethodAccessor(Method var1) {
        checkInitted();
        if (noInflation && !ReflectUtil.isVMAnonymousClass(var1.getDeclaringClass())) {
            return (new MethodAccessorGenerator()).generateMethod(var1.getDeclaringClass(), var1.getName(), var1.getParameterTypes(), var1.getReturnType(), var1.getExceptionTypes(), var1.getModifiers());
        } else {
            NativeMethodAccessorImpl var2 = new NativeMethodAccessorImpl(var1);
            DelegatingMethodAccessorImpl var3 = new DelegatingMethodAccessorImpl(var2);
            var2.setParent(var3);
            return var3;
        }
    

从上面代码可以看出:实际的MethodAccessor实现有两个版本
(1)一个是Java实现的。Java实现的版本在初始化时需要较多时间,但长久来说性能较好;
(2)另一个是native code实现的。native版本正好相反,启动时相对较快,但运行时间长了之后速度就比不过Java版了。这是HotSpot的优化方式带来的性能特性,同时也是许多虚拟机的共同点:跨越native边界会对优化有阻碍作用,它就像个黑箱一样让虚拟机难以分析也将其内联,于是运行时间长了之后反而是托管版本的代码更快些。
为了权衡两个版本的性能,Sun的JDK使用了inflation的技巧:让Java方法在被反射调用时,开头若干次使用native版,等反射调用次数超过阈值(15次)时则生成一个专用的MethodAccessor实现类,生成其中的invoke()方法的字节码,以后对该Java方法的反射调用就会使用Java版。

验证下

编译上述代码,并在执行MethodTest 时加入-XX:+TraceClassLoading参数(或者-verbose:class或者直接-verbose都行),如下:

java -XX:+TraceClassLoading MethodTest 

如果用IDEA,可以通过

点击Edit Configurations配置

image

可以看到输出了一大堆log,把其中相关的部分截取出来如下

huige:测试:0
huige:测试:1
huige:测试:2
huige:测试:3
huige:测试:4
huige:测试:5
huige:测试:6
huige:测试:7
huige:测试:8
huige:测试:9
huige:测试:10
huige:测试:11
huige:测试:12
huige:测试:13
huige:测试:14
[Loaded sun.reflect.ClassFileConstants from D:\project\Java\jdk1.8.0_101\jre\lib\rt.jar]
[Loaded sun.reflect.AccessorGenerator from D:\project\Java\jdk1.8.0_101\jre\lib\rt.jar]
[Loaded sun.reflect.MethodAccessorGenerator from D:\project\Java\jdk1.8.0_101\jre\lib\rt.jar]
[Loaded sun.reflect.ByteVectorFactory from D:\project\Java\jdk1.8.0_101\jre\lib\rt.jar]
[Loaded sun.reflect.ByteVector from D:\project\Java\jdk1.8.0_101\jre\lib\rt.jar]
[Loaded sun.reflect.ByteVectorImpl from D:\project\Java\jdk1.8.0_101\jre\lib\rt.jar]
[Loaded sun.reflect.ClassFileAssembler from D:\project\Java\jdk1.8.0_101\jre\lib\rt.jar]
[Loaded sun.reflect.UTF8 from D:\project\Java\jdk1.8.0_101\jre\lib\rt.jar]
[Loaded sun.reflect.Label from D:\project\Java\jdk1.8.0_101\jre\lib\rt.jar]
[Loaded sun.reflect.Label$PatchInfo from D:\project\Java\jdk1.8.0_101\jre\lib\rt.jar]
[Loaded sun.reflect.MethodAccessorGenerator$1 from D:\project\Java\jdk1.8.0_101\jre\lib\rt.jar]
[Loaded sun.reflect.ClassDefiner from D:\project\Java\jdk1.8.0_101\jre\lib\rt.jar]
[Loaded sun.reflect.ClassDefiner$1 from D:\project\Java\jdk1.8.0_101\jre\lib\rt.jar]
[Loaded sun.reflect.GeneratedMethodAccessor1 from __JVM_DefineClass__]
huige:测试:15

可以看到在执行16次时被触发了,导致JVM新加载了一堆类,其中就包括[Loaded sun.reflect.GeneratedMethodAccessor1 from JVM_DefineClass]这么一行

具体实现如下:

(1)MethodAccessor实现版本:开头若干次使用native

通过DelegatingMethodAccessorImpl实现的,代码如下:

class DelegatingMethodAccessorImpl extends MethodAccessorImpl {
    private MethodAccessorImpl delegate;

    DelegatingMethodAccessorImpl(MethodAccessorImpl var1) {
        this.setDelegate(var1);
    }

    public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
        return this.delegate.invoke(var1, var2);
    }

  //var1就是NativeMethodAccessorImpl var1 = new NativeMethodAccessorImpl(var1);
    void setDelegate(MethodAccessorImpl var1) {
        this.delegate = var1;
    }

Method.invoke()调用时,同时调用sun.reflect.DelegatingMethodAccessorImpl#invoke方法,即调用sun.reflect.NativeMethodAccessorImpl#invoke方法,代码如下:

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()方法被调用时,都会增加一个调用次数计数器numInvocations,看超过阈值没有;一旦超过,则调用MethodAccessorGenerator.generateMethod()来生成Java版的MethodAccessor的实现类,并且改变DelegatingMethodAccessorImpl所引用的MethodAccessor为Java版。后续经由DelegatingMethodAccessorImpl.invoke()调用到的就是Java版的实现了。

注意到关键的invoke0()方法是个native方法。它在HotSpot VM里是由JVM_InvokeMethod()函数所支持的:

JNIEXPORT jobject JNICALL Java_sun_reflect_NativeMethodAccessorImpl_invoke0  
(JNIEnv *env, jclass unused, jobject m, jobject obj, jobjectArray args)  
{  
    return JVM_InvokeMethod(env, m, obj, args);  
}  
JVM_ENTRY(jobject, JVM_InvokeMethod(JNIEnv *env, jobject method, jobject obj, jobjectArray args0))  
  JVMWrapper("JVM_InvokeMethod");  
  Handle method_handle;  
  if (thread->stack_available((address) &method_handle) >= JVMInvokeMethodSlack) {  
    method_handle = Handle(THREAD, JNIHandles::resolve(method));  
    Handle receiver(THREAD, JNIHandles::resolve(obj));  
    objArrayHandle args(THREAD, objArrayOop(JNIHandles::resolve(args0)));  
    oop result = Reflection::invoke_method(method_handle(), receiver, args, CHECK_NULL);  
    jobject res = JNIHandles::make_local(env, result);  
    if (JvmtiExport::should_post_vm_object_alloc()) {  
      oop ret_type = java_lang_reflect_Method::return_type(method_handle());  
      assert(ret_type != NULL, "sanity check: ret_type oop must not be NULL!");  
      if (java_lang_Class::is_primitive(ret_type)) {  
        // Only for primitive type vm allocates memory for java object.  
        // See box() method.  
        JvmtiExport::post_vm_object_alloc(JavaThread::current(), result);  
      }  
    }  
    return res;  
  } else {  
    THROW_0(vmSymbols::java_lang_StackOverflowError());  
  }  
JVM_END  

其中的关键又是Reflection::invoke_method()

// This would be nicer if, say, java.lang.reflect.Method was a subclass  
// of java.lang.reflect.Constructor  
  
oop Reflection::invoke_method(oop method_mirror, Handle receiver, objArrayHandle args, TRAPS) {  
  oop mirror             = java_lang_reflect_Method::clazz(method_mirror);  
  int slot               = java_lang_reflect_Method::slot(method_mirror);  
  bool override          = java_lang_reflect_Method::override(method_mirror) != 0;  
  objArrayHandle ptypes(THREAD, objArrayOop(java_lang_reflect_Method::parameter_types(method_mirror)));  
  
  oop return_type_mirror = java_lang_reflect_Method::return_type(method_mirror);  
  BasicType rtype;  
  if (java_lang_Class::is_primitive(return_type_mirror)) {  
    rtype = basic_type_mirror_to_basic_type(return_type_mirror, CHECK_NULL);  
  } else {  
    rtype = T_OBJECT;  
  }  
  
  instanceKlassHandle klass(THREAD, java_lang_Class::as_klassOop(mirror));  
  methodOop m = klass->method_with_idnum(slot);  
  if (m == NULL) {  
    THROW_MSG_0(vmSymbols::java_lang_InternalError(), "invoke");  
  }  
  methodHandle method(THREAD, m);  
  
  return invoke(klass, method, receiver, override, ptypes, rtype, args, true, THREAD);  
}  
(2)MethodAccessor实现版本:java版本MethodAccessorGenerator

MethodAccessorGenerato如何实现的呢?
具体实现在sun.reflect.MethodAccessorGenerator#generate方法
代码如下:

private MagicAccessorImpl generate(final Class<?> var1, String var2, Class<?>[] var3, Class<?> var4, Class<?>[] var5, int var6, boolean var7, boolean var8, Class<?> var9) {
        //......
      //这里生成[Loaded sun.reflect.GeneratedMethodAccessor1 from __JVM_DefineClass__]
        final String var13 = generateName(var7, var8);
        //。。。。。。
        this.targetMethodRef = this.asm.cpi();
        if (var7) {
            //构建newInstance
            this.asm.emitConstantPoolUTF8("newInstance");
        } else {
           //构建invoke
            this.asm.emitConstantPoolUTF8("invoke");
        }
      //.........
    }

最后生成的Java版MethodAccessor大致如下

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 {  
            TestClassLoad target = (TestClassLoad) obj;             
            String arg0 = (String) args[0];  
            Integer arg1 = (Integer ) args[1];  
        } catch (ClassCastException e) {  
            throw new IllegalArgumentException(e.toString());  
        } catch (NullPointerException e) {  
            throw new IllegalArgumentException(e.toString());  
        }  
        // 调用目标方法
        try {  
            target.test(arg0,arg1 );  
        } catch (Throwable t) {  
            throw new InvocationTargetException(t);  
        }  
    }  
}  
总结

就反射调用而言,这个invoke()方法非常干净(然而就“正常调用”而言这额外开销还是明显的)。注意到参数数组被拆开了,把每个参数都恢复到原本没有被Object[]包装前的样子,然后对目标方法做正常的invokevirtual调用。由于在生成代码时已经循环遍历过参数类型的数组,生成出来的代码里就不再包含循环了。
当该反射调用成为热点时,它甚至可以被内联到靠近Method.invoke()的一侧,大大降低了反射调用的开销。而native版的反射调用则无法被有效内联,因而调用开销无法随程序的运行而降低。
虽说Sun的JDK这种实现方式使得反射调用方法成本比以前降低了很多,但Method.invoke()本身要用数组包装参数;而且每次调用都必须检查方法的可见性(在Method.invoke()里),也必须检查每个实际参数与形式参数的类型匹配性(在NativeMethodAccessorImpl.invoke0()里或者生成的Java版MethodAccessor.invoke()里);而且Method.invoke()就像是个独木桥一样,各处的反射调用都要挤过去,在调用点上收集到的类型信息就会很乱,影响内联程序的判断,使得Method.invoke()自身难以被内联到调用方。
相比之下MethodHandle则更有潜力,在其功能完全实现后能达到比普通反射调用方法更高的性能。在使用MethodHandle来做反射调用时,MethodHandle.invoke()的形式参数与返回值类型都是准确的,所以只需要在链接方法的时候才需要检查类型的匹配性,而不必在每次调用时都检查。而且MethodHandle是不可变值,在创建后其内部状态就不会再改变了;JVM可以利用这个知识而放心的对它做激进优化,例如将实际的调用目标内联到做反射调用的一侧。

参考

关于反射调用方法的一个log

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容