11 虚拟机字节码执行引擎_对动态类型语言支持

1 动态/静态类型语言比较

对比1 对比2 对比3 举例
动态 运行时进行类型检查 在运行期确定类型,灵活 变量无类型而变量值才有类型 Groovy、JavaScript、Lua、PHP、Python
静态 编译时检查 编译期确定 ,稳定 变量必须有明确类型 java、C++

差异的本质原因:

  1. java:javac编译时就生成方法的符号引用,并作为方法调用指令的参数存储到Class文件中。符号引用包含:方法属于哪个类/接口、方法的名字、参数顺序、参数类型、方法返回值等信息,jvm通过符号引用翻译出直接引用。
  2. JavaScript:js编译器在编译时最多只能确定方法名称、参数、返回值等信息,而不会去确定方法所属类型

Java例子说明:

public class PrintfTest {
    public static void main(String[] args) {
        System.out.println("hello world");
    }
}

编译:javac ./PrintfTest.java
查看字节码内容:javap -verbose ./PrintfTest.class

JavaScript例子说明:

用var来定义变量或者方法返回值的类型

2 java与动态类型

  1. java的愿景包括:支持他语言运行于Java虚拟机之上
  2. jvm对动态类型语言的支持不足,主要表现在方法调用方面:JDK7以前的字节码指令集中,4条方法调用指令(invokevirtual、invokespecial、invokestatic、 invokeinterface)的第一个参数都是被调用的方法的符号引用(CONSTANT_Methodref_info或者 CONSTANT_InterfaceMethodref_info常量),但动态类型语言只有在运行期才能确定方法的接收者
  3. 初步的解决方案:编译时留个占位符类型,运行时动态生成字节码实现具体类型到占位符的适配,但该方式增加复杂度和性能开销
  4. 在Java虚拟机层面的彻底解决方案:invokedynamic指令以及java.lang.invoke包

3 java.lang.invoke包

  1. jdk7中加入的包,以方法句柄(Method Handle)方式动态确定目标方法 【原来只能依靠符号引用来确定方法】(不纠结句柄的字面意思)

看例子:

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import static java.lang.invoke.MethodHandles.lookup;
public class MethodHandleTest {
    static class ClassA {
        public void println(String s) {
            System.out.println(s);
        }
    }
    private static MethodHandle getPrintlnMH(Object receiver) throws Throwable {
        // MethodType:代表“方法类型”,包含了方法的返回值(methodType()的第一个参数)和具体参数(methodType()第二个及以后的参数)。
        MethodType mt = MethodType.methodType(void.class, String.class);
        
        // lookup()方法来自于MethodHandles.lookup,这句的作用是在指定类中查找符合给定的方法名称、方法类型,并且符合调用权限的方法句柄。
        // 因为这里调用的是一个虚方法,按照Java语言的规则,方法第一个参数是隐式的,代表该方法的接收者,也即this指向的对象,这个参数以前是放在参数列表中进行传递,现在提供了bindTo()方法来完成这件事情。
        return lookup().findVirtual(receiver.getClass(), "println", mt).bindTo(receiver);
    }

    public static void main(String[] args) throws Throwable {
        Object obj = System.currentTimeMillis() % 2 == 0 ? System.out : new ClassA();
        // 无论obj最终是哪个实现类,下面这句都能正确调用到println方法。
        getPrintlnMH(obj).invokeExact("world cup");
    }
}

说明:
方法getPrintlnMH()中实际上是模拟了invokevirtual指令的执行过程,将分派逻辑通过Java代码实现。

4 invokedynamic指令

  1. 该指令的第一个参数不再是代表方法符号引用的CONSTANT_Methodref_info常量,而是变为JDK7时新加入的CONSTANT_InvokeDynamic_info常量。
  2. CONSTANT_InvokeDynamic_info包含:引导方法(Bootstrap Method,该方法存放在新增的BootstrapMethods属性中)、方法类型(MethodType)、名称等信息
  3. invokedynamic指令和invoke包的实现原理是一样的,一个用上层代码和API来实现, 另一个用字节码和Class中其他属性、常量来完成
  4. Lambda表达式的实现依赖 invokedynamic指令
posted @ 2022-12-06 17:58  拿了桔子跑-范德依彪  阅读(28)  评论(0编辑  收藏  举报