博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Invokedynamic:Java的秘密武器
阅读量:6199 次
发布时间:2019-06-21

本文共 5731 字,大约阅读时间需要 19 分钟。

在Java 7的发布版中包含了多项新的特性,这些特性乍看上去Java开发人员对它们的使用非常有限,在我们之前的中,曾经对其进行过介绍。

\\

但是,其中有项特性对于实现Java 8中“头版标题”类型的特性来说至关重要(如lambdas和默认方法)。在本文中,我们将会深入学习invokedynamic,并阐述它对于Java平台以及像JRuby和Nashorn这样的JVM语言来讲为何如此重要。

\\

至少始于2007年,而第一次成功的动态调用发生在2008年8月26日。这比Oracle收购Sun还要早,按照大多数开发人员的标准,这个特性的研发已经持续了相当长的时间。

\\

值得注意的是,从Java 1.0到现在,invokedynamic是第一个新加入的Java字节码,它与已有的字节码invokevirtual、invokestatic、invokeinterface和invokespecial组合在了一起。已有的这四个操作码实现了Java开发人员所熟知的所有形式的方法分派(dispatch):

\\
  • invokevirtual——对实例方法的标准分派\\t
  • invokestatic——用于分派静态方法\\t
  • invokeinterface——用于通过接口进行方法调用的分派\\t
  • invokespecial——当需要进行非虚(也就是“精确”)分派时会用到\

有些开发人员可能会好奇平台为何需要这四种操作码,所以我们看一个简单的样例,这个样例会用到不同的调用操作码,以此来阐述它们之间的差异:

\\
\public class InvokeExamples {\    public static void main(String[] args) {\        InvokeExamples sc = new InvokeExamples();\        sc.run();\    }\\    private void run() {\        List ls = new ArrayList();\        ls.add(\"Good Day\");\\        ArrayList als = new ArrayList();\        als.add(\"Dydh Da\");\    }\}
\\

我们可以使用javap反汇编从而得到它所产生的字节码:

\\
\javap -c InvokeExamples.class\\public class kathik.InvokeExamples {\  public kathik.InvokeExamples();\    Code:\       0: aload_0\       1: invokespecial #1                  // Method java/lang/Object.\"\":()V\       4: return\\  public static void main(java.lang.String[]);\    Code:\       0: new           #2                  // class kathik/InvokeExamples\       3: dup\       4: invokespecial #3                  // Method \"\":()V\       7: astore_1\       8: aload_1\       9: invokespecial #4                  // Method run:()V\      12: return\\  private void run();\    Code:\       0: new           #5                  // class java/util/ArrayList\       3: dup\       4: invokespecial #6                  // Method java/util/ArrayList.\"\":()V\       7: astore_1\       8: aload_1\       9: ldc           #7                  // String Good Day\      11: invokeinterface #8,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z\      16: pop\      17: new           #5                  // class java/util/ArrayList\      20: dup\      21: invokespecial #6                  // Method java/util/ArrayList.\"\":()V\      24: astore_2\      25: aload_2\      26: ldc           #9                  // String Dydh Da\      28: invokevirtual #10                 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z\      31: pop\      32: return\}
\\

在这个示例中,展现了四个调用操作码中的三个(剩下的一个也就是invokestatic,是一个非常简单的扩展)。作为开始,我们可以看一下如下的两个调用(在run方法的字节11和28):

\\

ls.add(\"Good Day\")

\\

\\

als.add(\"Dydh Da\")

\\

在Java源码中它们看起来非常相似,但它们实际上却代表两种不同的字节码。

\\

对于javac来说,变量ls具有的静态类型是List\u0026lt;String\u0026gt;,而List是一个接口。所以,在运行时方法表(通常称为“vtable”)中,add()方法的精确位置还没有在编译时确定。因此,源码编译器会生成一个invokeinterface指令,将实际的方法查找推迟到运行期,也就是当ls的实际vtable能够探查到并且add()方法的位置能够找到的时候。

\\

与之相反,对als.add(\"Dydh Da\")的调用是通过als来执行的,这里的静态类型是类类型(class type)——ArrayList\u0026lt;String\u0026gt;。这意味着在vtable中,方法的位置在编译期是可知的。因此,javac会针对这个精确的vtable条目生成一个invokevirtual指令。不过,最终的方法选择依然是在运行期确定的,因为这里还有方法重写(overriding)的可能性,但是vtable slot在编译期就已经确定了。

\\

除此之外,这个样例还展现了invokespecial的两个使用场景。这个操作码用于在运行时确定如何分派的场景之中,具体来讲,在这里没有方法重写的需求,另外这也不可能实现。样例中所阐述的场景是private methodssuper calls,这些方法在编译期是可知的,并且无法进行重写。

\\

细心的读者可能已经发现,对Java方法的所有调用都编译成了四个操作码中的某一个,那么问题就来了——invokedynamic是做什么的,它对于Java开发人员有什么用处呢?

\\

这个特性的主要目标在于创建一个字节码,用于处理新型的方法分派——它的本质是允许应用级别的代码来确定执行哪一个方法调用,只有在调用要执行的时候,才会进行这种判断。这样的话,相对于Java平台之前所提供的编程风格,允许语言和框架的编写人员支持更加动态的编码风格。

\\

它的目的在于由用户代码通过方法句柄API(method handles API)在运行时确定如何分派,同时避免反射带来的性能惩罚和安全问题。实际上,invokedynamic所宣称的目标就是一旦该特性足够成熟,它的速度要像常规的方法分派(invokevirtual)一样快。

\\

当Java 7发布的时候,JVM就已经支持执行新的字节码了,但是不管提交什么样的Java代码,javac都不会产生包含invokedynamic的字节码。这项特性用来支持JRuby和其他运行在JVM上的动态语言。

\\

在Java 8中,这发生了变化,在实现lambda表达式和默认方法时,底层会生成和使用invokedynamic,它同时还会作为Nashorn的首选分派机制。但是,对于Java应用的开发人员来说,依然没有直接的方式实现完全的动态方法处理(resolution)。也就是说,Java语言并没有提供关键字或库来创建通用的invokedynamic调用点(call site)。这意味着,尽管这种机制的功能非常强大,但它对于大多数的Java开发人员来说依然有些陌生。接下来,我们看一下如何在自己的代码中使用这项技术。

\\

方法句柄简介

\\

要让invokedynamic正常运行,一个核心的概念就是方法句柄(method handle)。它代表了一个可以从invokedynamic调用点进行调用的方法。这里的基本理念就是每个invokedynamic指令都会与一个特定的方法关联(也就是引导方法或BSM)。当解释器(interpreter)遇到invokedynamic指令的时候,BSM会被调用。它会返回一个对象(包含了一个方法句柄),这个对象表明了调用点要实际执行哪个方法。

\\

在一定程度上,这与反射有些类似,但是反射有它的局限性,这些局限性使它不适合与invokedynamic协作使用。Java 7 API中加入了java.lang.invoke.MethodHandle(及其子类),通过它们来代表invokedynamic指向的方法。为了实现操作的正确性,MethodHandle会得到JVM的一些特殊处理。

\\

理解方法句柄的一种方式就是将其视为以安全、现代的方式来实现反射的核心功能,在这个过程会尽可能地保证类型的安全。invokedynamic需要方法句柄,另外它们也可以单独使用。

\\

方法类型

\\

一个Java方法可以视为由四个基本内容所构成:

\\
  • 名称\\t
  • 签名(包含返回类型)\\t
  • 定义它的类\\t
  • 实现方法的字节码\

这意味着如果要引用某个方法,我们需要有一种有效的方式来表示方法签名(而不是反射中强制使用的令人讨厌的Class\u0026lt;?\u0026gt;[] hack方式)。

\\

接下来我们采用另外的方式,方法句柄首先需要的一个构建块就是表达方法签名的方式,以便于查找。在Java 7引入的Method Handles API中,这个角色是由java.lang.invoke.MethodType类来完成的,它使用一个不可变的实例来代表签名。要获取MethodType,我们可以使用methodType()工厂方法。这是一个参数可变(variadic)的方法,以class对象作为参数。

\\

第一个参数所使用的class对象,对应着签名的返回类型;剩余参数中所使用的class对象,对应着签名中方法参数的类型。例如:

\\
\//toString()的签名\MethodType mtToString = MethodType.methodType(String.class);\\// setter方法的签名\MethodType mtSetter = MethodType.methodType(void.class, Object.class);\\// Comparator中compare()方法的签名\MethodType mtStringComparator = MethodType.methodType(int.class, String.class, String.class);
\\

现在我们就可以使用MethodType,再组合方法名称以及定义方法的类来查找方法句柄。要实现这一点,我们需要调用静态的MethodHandles.lookup()方法。这样的话,会给我们一个“查找上下文(lookup context)”,这个上下文基于当前正在执行的方法(也就是调用lookup()的方法)的访问权限。

\\

查找上下文对象有一些以“find”开头的方法,例如,findVirtual()、findConstructor()、findStatic()等。这些方法将会返回实际的方法句柄,需要注意的是,只有在创建查找上下文的方法能够访问(调用)被请求方法的情况下,才会返回句柄。这与反射不同,我们没有办法绕过访问控制。换句话说,方法句柄中并没有与setAccessible()对应的方法。例如:

\\
\public MethodHandle getToStringMH() {\    MethodHandle mh = null;\    MethodType mt = MethodType.methodType(String.class);\    MethodHandles.Lookup lk = MethodHandles.lookup();\\    try {\        mh = lk.findVirtual(getClass(), \"toString\

转载地址:http://iinca.baihongyu.com/

你可能感兴趣的文章
ajax 动态生成二级联动下拉列表
查看>>
ssh长时间连接腾讯云centos服务器
查看>>
利用HTML5分片上传超大文件
查看>>
WVGA与HVGA、QVGA详细解答
查看>>
Java Platform Enterprise Edition (Java EE) Specification v6翻译的引言
查看>>
c primer plus(第五版)读书笔计 第五章(1)
查看>>
SQL约束 CONSTRAINT
查看>>
文件包含漏洞检测工具fimap
查看>>
Linux 上SSH 服务的配置和管理
查看>>
Windows PowerShell:(4)使用ISE
查看>>
linux学习笔记(一)
查看>>
硅谷正在泡沫中,以及什么会刺破它?
查看>>
遇到单臂路由的一个问题
查看>>
ceph最新版安装教程
查看>>
Android ContentObserver
查看>>
查看Oracle当前用户下的信息(用户,表视图,索引,表空间,同义词等)
查看>>
django model的get和filter方法的区别
查看>>
用思科做HSRP
查看>>
Sqoop实现 Hadoop(Hive)与Mysql间数据传递
查看>>
RHEL6.3内核升级实战篇
查看>>