目录
在上一篇《Hotspot JNI和字节码方法调用源码解析》中讲到了 invokedynamic动态方法调用指令,从《Java虚拟机规范》中invokedynamic的描述可知invokedynamic的底层实现是基于java.lang.invoke.MethodHandle的,下面来详细探讨下MethodHandle的用法以及同Java Reflect的差异对比。
一、编程语言类型
1、解释型与编译型语言
解释型语言:是指通过解释器解释执行的语言,解释型语言不需要编译成与底层硬件平台直接交互的机器码语言,只要能被解释器识别即可,可以是源码或者类似于字节码的中间代码,典型的如JavaScript,Python,对应的解释器是谷歌V8 JavaScript执行引擎引入的解释器 Ignition,官方Python解释器CPython。
编译型语言:是指可以通过编译器编译变成可以直接在底层硬件平台上直接运行的机器码,可以与底层硬件直接交互的语言,典型的如C/C++,C-object,操作系统如Linux,windows等,大小硬件设备的驱动程序基本都是用C写的。
解释型语言因为必须经过中间的解释器解释执行,所以性能相对而言要差,但是语法规则可以更灵活,且因为解释器屏蔽了同底层硬件交互的细节,解释型语言学习成本更低,跨平台能力更强。编译型语言必须与底层硬件平台直接交互,能够基于底层平台特性做特定优化,所以性能更好,也是因此需要综合考虑并兼顾不同底层硬件平台的特性,代码更加复杂,学习成本高。
Java可以通过默认的字节码模板解释器解释执行,也可以通过JIT即时编译器编译执行,通过-X+int选项指定以解释方式执行,通过-Xcomp选项指定以编译方式执行,通过-Xmixed,以解释+热点代码编译的方式执行,默认是-Xmixed。
2、静态类型语言与动态类型语言
静态类型语言:是指对数据类型/方法调用的检查是在编译期执行的,根据变量声明的类型来检查,典型的如Java
动态类型语言:是指对数据类型/方法调用的检查是在运行期执行的,根据变量实际的值类型来检查,典型的如JavaScript
Java的测试用例如下:
public class TypeTest {
public static void test(String s){
System.out.println("test:"+s);
}
public static void main(String[] args) {
int a=12;
TypeTest.test(a);
}
}
执行javac编译报错,如下图:
如果是方法调用如TypeTest.test(a);会检查TypeTest类是否包含静态test方法。
JavaScript的测试用例如下:
function add(a,b){
return a+b;
}
console.log(add(1,2));
console.log(add("a",2));
执行结果如下:
声明add方法时不需要声明变量a和变量b的类型,没有声明肯定就不会在编译时校验类型了,解释执行的时候会根据变量a和变量b的实际类型决定a+b的结果,如果两个都是数字则a+b表示两个数字相加,如果其中一个表示字符串则a+b表示将a和b以字串符的形式连接起来。
3、强类型语言和弱类型语言
强类型语言是指一个变量如果被声明成什么类型了,在整个运行期该变量就一直是该类型,除非被强转成其他数据类型,典型的如Java
弱类型语言是指一个变量在声明时不需要指定特定的类型,而是可以在运行时赋值成任一数据类型的值,典型的如JavaScipt。
Java的测试用例如下:
public class TypeTest {
public static void main(String[] args) {
int a=12;
long b=13L;
a=b;
}
}
javac编译报错:
上述代码如果改成a=(int)b就不会报错,即将b强转成int赋值给a,b的类型依然是long。
javaScript的测试用例如下:
var a=12;
console.log(a);
a="test";
console.log(a);
执行结果如下:
变量a先是被赋值成整数类型,接着又被赋值成字符串类型,都可以正常执行。
4、动态语言和静态语言
动态语言:指在运行时可以改变其结构的语言,如新的属性、函数、对象,甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化,典型的如JavaScript
静态语言:指运行时结构不可变的语言,典型的如Java
JavaScript的测试用例如下:
class cla{
};
cla.a=1;
console.log(cla.a);
cla.test=function(){console.log("test");};
cla.test();
代码执行结果如下:
a和test不属于cla中定义的属性,而是在运行时动态添加的
Java测试用例如下:
public class TypeTest {
public int a;
public static void main(String[] args) {
TypeTest typeTest=new TypeTest();
typeTest.a=1;
typeTest.b=2;
}
}
javac编译报错,如下图:
属性a是TypeTest中声明的所以typeTest.a=1;没有报错,属性b不是TypeTest中声明的,所以报错找不到符号。
参考:编译型与解释型、动态语言与静态语言、强类型语言与弱类型语言的区别
二、java.lang.invoke
1、JSR-292
java.lang.invoke是Java7实现JSR-292而引入的,为了在缺乏静态类型信息,运行时才能获取类型信息的情况下能够高效和灵活的执行方法调用,即在方法调用层面上提供对动态类型语言的支持,如执行obj.println("hello world"); 不再要求obj必须是java.io.PrintStream类型,只要在运行期obj定义了println方法即可。
因为之前定义的四个方法调用指令invokevirtual invokespecial invokestatic invokeinterface的第一个参数都是被调用方法的符号引用,根据符号引用可以解析出被调用方法的接收对象的类型和方法定义,即这四个指令要求在编译期必须确认被调用方法的接收对象的对象类型,如编译obj.println("hello world"); 必须确认obj的具体的类型,然后检查该类型是否定义了println方法,检查通过再据此生成一个被调用方法的符号引用。为了能够在JVM字节码指令层面实现在运行期才确认被调用方法的接收对象的类型,JVM配套的引入了一个新的方法调用指令invokedynamic,该指令同时也是Java8实现lamada的技术基础。注意java.lang.invoke包的底层实现并没有依赖invokedynamic指令,在非lamada下javac目前也不会生成invokedynamic指令,invokedynamic指令主要给同样是JVM上运行的动态类型语言使用,如Groovy。
测试用例如下:
package jni;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Arrays;
import java.util.List;
class TestA{
public void println(String s){
System.out.println("TestA println:"+s);
}
}
class TestB{
public void println(String s){
System.out.println("TestB println:"+s);
}
}
public class MethodHandlerTest {
public static void lamadaTest(){
List<String> s= Arrays.asList("a","b","c");
s.stream().forEach((String str) ->{System.out.println(str);});
}
public static void println(Object obj,String s) throws Throwable{
MethodType mt=MethodType.methodType(void.class,String.class);
MethodHandle methodHandle=MethodHandles.lookup().findVirtual(obj.getClass(),"println",mt);
methodHandle.bindTo(obj).invokeExact(s);
}
public static void main(String[] args) throws Throwable{
String test="Hello World";
println(new TestA(),test);
println(new TestB(),test);
println(System.out,test);
lamadaTest();
}
}
上述用例中执行println方法时,obj的具体类型是不确定的,是在运行时才确认的,执行结果如下:
通过javap -v可以查看MethodHandlerTest的字节码,如下:
public static void lamadaTest();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=1, args_size=0
0: iconst_3
1: anewarray #2 // class java/lang/String
4: dup
5: iconst_0
6: ldc #3 // String a
8: aastore
9: dup
10: iconst_1
11: ldc #4 // String b
13: aastore
14: dup
15: iconst_2
16: ldc #5 // String c
18: aastore
19: invokestatic #6 // Method java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List;
22: astore_0
23: aload_0
24: invokeinterface #7, 1 // InterfaceMethod java/util/List.stream:()Ljava/util/stream/Stream;
29: invokedynamic #8, 0 // InvokeDynamic #0:accept:()Ljava/util/function/Consumer;
34: invokeinterface #9, 2 // InterfaceMethod java/util/stream/Stream.forEach:(Ljava/util/function/Consumer;)V
39: return
LineNumberTable:
line 24: 0
line 25: 23
line 26: 39
LocalVariableTable:
Start Length Slot Name Signature
23 17 0 s Ljava/util/List;
LocalVariableTypeTable:
Start Length Slot Name Signature
23 17 0 s Ljava/util/List<Ljava/lang/String;>;
public static void println(java.lang.Object, java.lang.String) throws java.lang.Throwable;
descriptor: (Ljava/lang/Object;Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=4, args_size=2
0: getstatic #2 // Field java/lang/Void.TYPE:Ljava/lang/Class;
3: ldc #3 // class java/lang/String
5: invokestatic #4 // Method java/lang/invoke/MethodType.methodType:(Ljava/lang/Class;Ljava/lang/Class;)Ljava/lang/invoke/MethodType;
8: astore_2
9: invokestatic #5 // Method java/lang/invoke/MethodHandles.lookup:()Ljava/lang/invoke/MethodHandles$Lookup;
12: aload_0
13: invokevirtual #6 // Method java/lang/Object.getClass:()Ljava/lang/Class;
16: ldc #7 // String println
18: aload_2
19: invokevirtual #8 // Method java/lang/invoke/MethodHandles$Lookup.findVirtual:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;
22: astore_3
23: aload_3
24: aload_0
25: invokevirtual #9 // Method java/lang/invoke/MethodHandle.bindTo:(Ljava/lang/Object;)Ljava/lang/invoke/MethodHandle;
28: aload_1
29: invokevirtual #10 // Method java/lang/invoke/MethodHandle.invokeExact:(Ljava/lang/String;)V
32: return
LineNumberTable:
line 22: 0
line 23: 9
line 24: 23
line 25: 32
LocalVariableTable:
Start Length Slot Name Signature
0 33 0 obj Ljava/lang/Object;
0 33 1 s Ljava/lang/String;
9 24 2 mt Ljava/lang/invoke/MethodType;
23 10 3 methodHandle Ljava/lang/invoke/MethodHandle;
Exceptions:
throws java.lang.Throwable
public static void main(java.lang.String[]) throws java.lang.Throwable;
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC,