11 虚拟机字节码执行引擎_对动态类型语言支持
1 动态/静态类型语言比较
对比1 | 对比2 | 对比3 | 举例 | |
---|---|---|---|---|
动态 | 运行时进行类型检查 | 在运行期确定类型,灵活 | 变量无类型而变量值才有类型 | Groovy、JavaScript、Lua、PHP、Python |
静态 | 编译时检查 | 编译期确定 ,稳定 | 变量必须有明确类型 | java、C++ |
差异的本质原因:
- java:javac编译时就生成方法的符号引用,并作为方法调用指令的参数存储到Class文件中。符号引用包含:方法属于哪个类/接口、方法的名字、参数顺序、参数类型、方法返回值等信息,jvm通过符号引用翻译出直接引用。
- 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与动态类型
- java的愿景包括:支持他语言运行于Java虚拟机之上
- jvm对动态类型语言的支持不足,主要表现在方法调用方面:JDK7以前的字节码指令集中,4条方法调用指令(invokevirtual、invokespecial、invokestatic、 invokeinterface)的第一个参数都是被调用的方法的符号引用(CONSTANT_Methodref_info或者 CONSTANT_InterfaceMethodref_info常量),但动态类型语言只有在运行期才能确定方法的接收者
- 初步的解决方案:编译时留个占位符类型,运行时动态生成字节码实现具体类型到占位符的适配,但该方式增加复杂度和性能开销
- 在Java虚拟机层面的彻底解决方案:invokedynamic指令以及java.lang.invoke包。
3 java.lang.invoke包
- 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指令
- 该指令的第一个参数不再是代表方法符号引用的CONSTANT_Methodref_info常量,而是变为JDK7时新加入的CONSTANT_InvokeDynamic_info常量。
- CONSTANT_InvokeDynamic_info包含:引导方法(Bootstrap Method,该方法存放在新增的BootstrapMethods属性中)、方法类型(MethodType)、名称等信息
- invokedynamic指令和invoke包的实现原理是一样的,一个用上层代码和API来实现, 另一个用字节码和Class中其他属性、常量来完成
- Lambda表达式的实现依赖 invokedynamic指令