论坛首页 Java企业应用论坛

通过代码简单介绍JDK 7的MethodHandle,并与.NET的委托对比

浏览 20449 次
该帖已经被评为精华帖
作者 正文
   发表时间:2009-09-28  
又有看的了...high呀
0 请登录后投票
   发表时间:2009-09-28  
starfeng 写道
引用
定位到一个java方法,其实只需要类型(Class),方法名及参数即可。
...

MethodHandle版:
private static void test(java.dyn.MethodHandle);
    Signature: (Ljava/dyn/MethodHandle;)V
    flags: ACC_PRIVATE, ACC_STATIC    LineNumberTable:
      line 7: 0
      line 8: 8
      line 7: 15
      line 10: 21
    Code:
      stack=4, locals=2, args_size=1
         0: iconst_0     
         1: istore_1     
         2: iload_1      
         3: ldc           #2                  // int 100000
         5: if_icmpge     21
         8: aload_0      
         9: iconst_1     
        10: iconst_2     
        11: iconst_3     
        12: invokevirtual #3                  // Method java/dyn/MethodHandle.invoke:(III)V
...
普通反射版:
private static void test(java.lang.reflect.Method) throws java.lang.Throwable;
    Signature: (Ljava/lang/reflect/Method;)V
    flags: ACC_PRIVATE, ACC_STATIC    LineNumberTable:
      line 7: 0
      line 8: 8
      line 7: 39
      line 10: 45
    Code:
      stack=6, locals=2, args_size=1
         0: iconst_0     
         1: istore_1     
         2: iload_1      
         3: ldc           #2                  // int 100000
         5: if_icmpge     45
         8: aload_0      
         9: aconst_null  
        10: iconst_3     
        11: anewarray     #3                  // class java/lang/Object
        14: dup          
        15: iconst_0     
        16: iconst_1     
        17: invokestatic  #4                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        20: aastore      
        21: dup          
        22: iconst_1     
        23: iconst_2     
        24: invokestatic  #4                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        27: aastore      
        28: dup          
        29: iconst_2     
        30: iconst_3     
        31: invokestatic  #4                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        34: aastore      
        35: invokevirtual #5                  // Method java/lang/reflect/Method.invoke:(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
...


写得很多,看得也累。。。但感觉整个就是答错了方向.
再加上后面回复的人,有好几个在不懂装懂的,整个就一误人子弟的贴。

MethodHandle我还没来得及深究,但汇编,java字节码还是有不少了解的。
如果你认为性能差距的原因是来自:
引用
17: invokestatic  #4                  // Method java/lang/Integer.valueOf:

这类代码,那么,麻烦你改一改测试类,统一成
 private static void doNothing(Integer x, Integer y, Integer z) { } 

再比较字节码。
另外,
引用
35: invokevirtual #5                  // Method java/lang/reflect/Method.invoke:(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;

这个代码和
引用
12: invokevirtual #3                  // Method java/dyn/MethodHandle.invoke:(III)V

相比,就调用本身来说,是没有差别的,唯一差别是在的两个方法的native c的实现上。如果你分析这段c的代码再得出性能差别的原因,才是找对了方向。

另外,如果我没搞错的话,MethodHandle引入返回值查找方法,其根本原因是来自动态语言的返回值与具体类型无关,和性能本身没什么关系。然后,
引用
如果只是要做Java的method overload resolution,当然只要参数类型不要返回值类型就够了,但了解class文件及JVM内部数据组织方式的话就会知道,方法的签名(signature)在class文件里是以方法描述符(method descriptor)的形式存在,而该描述符上是有返回值类型的。MethodHandles的API这么设计就是为了快,能更直接的访问VM里的信息,以最快的方式找到目标方法。

这个只是你自已的推论吧。这个就如同这么一个sql的比方:原先是查询是id=1, 你说,不够快,id=1&retType=2才更快。

如果MethodHandles.Lookup跟普通反射一样,只通过参数类型去搜索目标方法,那么它与JVM内部所持有的信息就有不匹配的地方,而需要去考虑可能遇到多个匹配结果的问题,然后产生异常发出来,速度自然就慢了。当前的API设计要求指定返回值类型,与JVM持有的信息一致,肯定能找到唯一的方法(或者是方法不存在),速度为何不应该更好?

我之所以选用接收int为参数的类型作为目标方法自然是有目的的,而那就是找一个反射调用是开销非常大的状况,拿来与MethodHandle对比。这样容易显示出差异。事实上一开始我写那两组测试的时候的doNothing就是无参的空方法,后来觉得效果要更明显些比较有趣,就改为接收多个int的。

starfeng 写道
再比较字节码。
另外,
引用
35: invokevirtual #5                  // Method java/lang/reflect/Method.invoke:(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;


这个代码和
引用
12: invokevirtual #3                  // Method java/dyn/MethodHandle.invoke:(III)V

相比,就调用本身来说,是没有差别的,唯一差别是在内部的native c的实现上。

单就“invokevirtual”指令来说,从字节码上是看不到区别。然而速度的差异来自两部分,
1、Method.invoke在Java代码的一侧有固有的额外开销,包括可变长度参数数组包装以及原始类型参数的自动装箱。即便目标方法接收的参数是引用类型的,没有原始类型参数的装箱开销,数组包装是无法避免的。即使调用method.invoke(null)也会导致一个空Object[]数组的创建。这个并不是单在那条“invokevirtual”上的开销。
starfeng 写道
那么,麻烦你改一改测试类,统一成
//...

这句话请您自己先试了,把结果贴出来,看看数组包装的代码是否仍然存在,而反射的开销是否仍然比MethodHandle大。

2、Method.invoke在VM内的一侧有固有的额外开销,并且会阻碍JIT编译器的优化;而MethodHandle的实现是它比普通反射的固有额外开销少,而且不会阻碍编译器的优化(当前实现尚未完善)。无论是通过MethodHandle.invoke还是通过Method.invoke,我们要达到的目的都是调用“实际目标方法”,而不想去关心这些invoke的实现;也就是说,如果这些invoke本身很复杂,对我们来说就是不利的。同样使用invokevirtual指令去调用“invoke”方法是事实,但跟invoke到实际目标方法的距离没有关系,而这“距离”正是两种方式的性能差异的第二个大头。

你该不会认为“native c”实现的东西的运行代价都小到可以忽略不计吧。另外HotSpot VM是用C++与内联汇编而不是C来实现的,准确说也不是“native c”。
我前面已经提到过Method.invoke会涉及的一些内部实现的方法,用它们做关键字相信你能搜到满意的信息。
简单的说,反射调用的过程中涉及多次安全检查,涉及查询类型信息去找到方法的实际指针,涉及包装类型到原始类型的拆箱,最后还有涉及参数数组解包装,通过stub把参数合适的放到栈上,最后才跑到实际目标方法里;如果其中一些安全检查的结果是可以被cache住的,但另外一些流程则每次调用都要经历,所以速度才慢。
相比之下,MethodHandle.invoke则直接的多。前面例子里用的大多是DirectMethodHandle,它直接指向目标方法,所有安全检查都是在创建MethodHandle的实例是就已经完成,调用MethodHandle.invoke完全不涉及再做安全检查的开销。它不涉及对目标方法的搜索,因为它持有的token信息让VM直接知道目标方法是哪一个。而且由于参数没有被数组包装,它不用经过stub去调整参数而可以直接把参数传递给实际的目标方法。总体来说,在没有被内联的前提下,它跟接口方法调用涉及的一些过程非常相似。现在它速度没有接口方法调用快只是因为JDK 7还在开发过程中,一些既定的实现目标还没达到而已。

嘛……欢迎争论~ =v=
不过请提出确实的依据。如果你调试过,读过相关源码,仍然得到你现在的结论,那我很高兴继续与你争论。
0 请登录后投票
   发表时间:2009-09-29  
看这情况应该引入新的关键字
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics