JVM 基础篇12 —— JVM中的动态类型语言支持

动态类型语言

在动态类型语言的类型检查的主体过程是在运行期而不是编译期。相应的在编译期进行类型检查的又被称为静态类型语言。

注:"动态类型语言"和经常容易弄混的"动态语言"以及"弱类型语言"是不同的概念

静态类型VS动态类型:

  • 静态类型的好处显而易见编译器可以提供严谨的类型检查,编码时就能发现相关错误而不是跑着跑着跳出来一个bug。。。这有利于代码的稳定性和更大规模的应用。
  • 动态类型的好处在于代码可能更加清晰和简洁,从而使得开发效率得以提升。

要对比的话可以通过C++、Java、Rust与Lua、Python、Golang清晰的看出差异。

JVM 与 动态类型语言

按照动态和静态类型语言的划分,乍一看Java算是静态类型语言,虽然也有人拿反射,Object说事说它是准动态类型语言,但是即便如此动态类型语言支持和JVM有半毛钱关系?事实上jvm不仅仅服务于Java,像Clojure、Groovy、Jython等都基于JVM平台,而它们都是动态类型语言。

在JDK 7以前字节码指令集中,四条方法调用指令(invokevirtual、invokespecial、invokestatic、invokeinterface)的第一个参数都是被调用的方法的符号引用(CONSTANT_Methodref_info或者CONSTANT_InterfaceMethodref_info常量),在上一节中说过,Class文件中保存的只是符号引用,而动态类型语言只有在运行期才能确定接收者类型。

所以,在JVM就不得不使用“曲线救国”的方式(如编译时留个占位符类型,运行时动态生成字节码实现具体类型到占位符类型的适配)来实现,这样势必让动态类型语言实现的复杂度增加,也可能带来额外的性能或者内存开销。尽管可以想一些办法(如Call Site Caching)让这些开销尽量变小,但这种底层问题终归是应当在虚拟机层次上去解决才最合适。

java.lang.invoke

方法句柄(method handle)是JSR 292(JDK 7)中引入的一个重要概念,它是对Java中方法、构造方法和域的一个强类型的可执行的引用。这也是句柄这个词的含义所在。通过方法句柄可以直接调用该句柄所引用的底层方法。从作用上来说,方法句柄的作用类似于反射中的Method类,但是方法句柄的功能更强大、使用更灵活、性能也更好。实际上,方法句柄和反射API也是可以协同使用。在Java标准库中,方法句柄由java.lang.invoke.MethodHandle类来表示。
如:

 public class MethodHandleTest {
     static class A {
         public void println(String s) {
             System.out.println(s);
         }
     }
     public static void main(String[] args) throws Throwable {
         Object obj = System.currentTimeMillis() % 2 == 0 ? System.out : new A();
         // 无论obj最终是哪个实现类,下面这句都能正确调用到println方法。
         getPrintlnMsg(obj).invokeExact("abc");
     }
     private static MethodHandle getPrintlnMsg(Object reveiver) throws Throwable {
         MethodType mt = MethodType.methodType(void.class, String.class);
         return lookup().findVirtual(reveiver.getClass(), "println", mt).bindTo(reveiver);
     }
 }
  • MethodType.methodType返回了一个方法类型,他的参数指定了这个方法的返回值(第一个参数)和方法具体参数(第二个参数)
  • lookup方法用于在指定的类中查找符合给定方法名称,方法类型和调用权限的方法句柄
  • 因为Java中方法的第一个参数是隐式的this,这个本来是在参数列表中传递,现在我们直接拿了方法的句柄,所以要通过bindTo将对象绑定到这个方法上。

MethodHandle方法和反射是十分相似的,但是MethodHandle更接近底层

  • Reflection和MethodHandle机制本质上都是在模拟方法调用,但是Reflection是在模拟Java代码层次的方法调用,而MethodHandle是在模拟字节码层次的方法调用。
  • Reflection中的java.lang.reflect.Method对象远比MethodHandle机制中的java.lang.invoke.MethodHandle对象所包含的信息来得多。
  • 由于MethodHandle是对字节码的方法指令调用的模拟,那理论上虚拟机在这方面做的各种优化(如方法内联),在MethodHandle上也应当可以采用类似思路去支持,而通过反射去调用方法则几乎不可能直接去实施各类调用点优化措施。

除了这些以外,最关键的是MethodHandle是可以服务于所有Java虚拟机上的语言,但是Reflection API只是为Java服务的。

方法句柄的类型

对于一个方法句柄来说,它的类型完全由它的参数类型和返回值类型来确定,而与它所引用的底层方法的名称和所在的类没有关系。比如引用String类的length方法和Integer类的intValue方法的方法句柄的类型就是一样的,因为这两个方法都没有参数,而且返回值类型都是int。

在得到一个方法句柄,即MethodHandle类的对象之后,可以通过其type方法来查看其类型。该方法的返回值是一个java.lang.invoke.MethodType类的对象。MethodType类的所有对象实例都是不可变的,类似于String类。所有对MethodType类对象的修改,都会产生一个新的MethodType类对象。两个MethodType类对象是否相等,只取决于它们所包含的参数类型和返回值类型是否完全一致。

MethodType类的对象实例只能通过MethodType类中的静态工厂方法来创建。这样的工厂方法有三类。第一类是通过指定参数和返回值的类型来创建MethodType,这主要是使用methodType方法的多种重载形式。使用这些方法的时候,至少需要指定返回值类型,而参数类型则可以是0到多个。返回值类型总是出现在methodType方法参数列表的第一个,后面紧接着的是0到多个参数的类型。类型都是由Class类的对象来指定的。如果返回值类型是void,可以用void.class或java.lang.Void.class来声明。(太多了,可自行了解java.lang.invoke包的使用不做过多说明)

invokedynamic

invokedynamic指令与MethodHandle机制的作用是一样的。每一处含有invokedynamic指令的位置都被称作动态调用点(Dynamically-Computed Call Site),这条指令的第一个参数变成了CONSTANT_InvokeDynamic_info常量,从这个新常量中可以得到3项信息:引导方法、方法类型和名称

引导方法是有固定的参数,并且返回值规定是java.lang.invoke.CallSite对象,这个对象代表了真正要执行的目标方法调用。

根据这个常量,虚拟机可以找到并且执行引导方法,从而获得一个CallSite对象,最终调用要执行的目标方法上。

更新时间:2020-08-03 22:29:45

本文由 寻非 创作,如果您觉得本文不错,请随意赞赏
采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
原文链接:https://www.zhouning.group/archives/jvm基础篇12jvm中的动态类型语言支持
最后更新:2020-08-03 22:29:45

评论

Your browser is out of date!

Update your browser to view this website correctly. Update my browser now

×