一、方法调用
invokestatic 静态方法调用
编译期间确定,且运行期间不会修改,这类调用不需要把对象加载到操作数栈里,只需要将所需要的参数入栈就可以操作了,效率比较高;
invokevirtual普通方法调用
运行期间确定,原因是受继承等因素影响编译期间无法确定,这类调用需要把对象引用、参数加载到操作数栈里。如果方法有返回则会把返回值压入栈顶。
invokespecial特殊方法调用
编译期间确定,原因是一般不会被改写:适用于1、构造器<init>;2、使用super关键字调用父类的方法3、private私有方法调用。
invokeinterface接口方法调用
运行期间确定,每个类在运行时都有一个vtable和itable
vtable虚方法表
在方法调用时是通过索引找到方法引用的。比如A类有123三个方法,B extends A,复写了方法2,同时新增了方法4。那么结构如下,这里需要注意的是子类会保留父类中方法的索引顺序,比如下面的方法2,在所有子类的索引顺序全是2。即子类会继承父类的vtable。这样JVM才会很方便的通过索引来调用
index |
方法引用 |
index |
方法引用 |
1 |
A/method1 |
1 |
A/method1 |
2 |
A/method2 |
2 |
B/method2 |
3 |
A/method3 |
3 |
A/method3 |
4 |
B/method4 |
itable接口方法表
它由偏移量和方法表组成,在调用接口方法时,JVM会在偏移量表中查找到对应的方法表位置和方法位置,然后在方法表中查找具体的方法实现。
invokedynamic动态方法调用
这个指令是在1.8中才有的。这个指令有一套对应的API,其作用就是把固化到JVM中的上述几个指令开放给程序员来调用;
public class Foo { public void print(String s){ System.out.println("hello "+ s); }
public static void main(String []args) throws Throwable { Foo foo = new Foo(); //定义方法签名,参数内容为返回值和入参 MethodType methodType = MethodType.methodType(void.class, String.class); /**返回方法句柄 * MethodHandles.lookup()返回一个上下文,通过find指令查找不同的方法,找到的用MethodType签名的方法称为 句柄 */ MethodHandle methodHandle = MethodHandles.lookup().findVirtual(Foo.class, "print", methodType); // hello world methodHandle.invokeExact(foo, "world"); } } |
二、特殊用法
泛型
在使用时确定,在编译时检查,其目的就是为了节省代码量。但存在很多兼容问题。因为泛型在编译成字节码后就不存在泛型信息(类型擦除)了。所以在使用时需要格外小心。下面是它的缺点:
1、泛型没有自己的.class对象,所以不存在List<Integer>.class,因为类型擦除后就是List.class;
2、泛型不能是基础类型,因为编译擦除后变变为Object类型,而int类型是不能存到Object中的;所以不存在List<int>.class
3、不能定义异常类型的异常,因为异常是在编译时存放在异常表中,如果不能确定类型就不能正常编译,比如pubic <T extends Throwable> foo()就是错误的定义;
4、不能定义为数组,因为Pair<Integer>[],在编译后就变成了Pair [],即Object可以存放任何类型,造成了不安全;
通过下表可以出这两种写法是一样的
public class Pair<T> { public T left; }
Pair<String> pair = new Pair<>(); String s = pair.left; |
aload 1 getfield 'com/jvm//Pair.left','Ljava/lang/Object;' checkcast 'java/lang/String' astore 2 |
class Pair1{ public Object left; } Pair1 pair1 = new Pair1(); s = (String)pair1.left; |
aload 3 getfield 'com/jvm/Pair1.left','Ljava/lang/Object;' checkcast 'java/lang/String' astore 2 |
反射
在java中Method.invoke调用的是MethodAccessor接口来实现反射的,此接口有两个实现类,在程序反射调用过程中都会使用,由两个参数控制:
sun.reflect.inflationThreshold=15以及sun.reflect.noInflation=false。当小于15次调用时则采用JNI方式即NativeMethodAccessorImpl,当调用次数大于15时则采用GeneratedMethodAcessor这是一种ASM实现。后者是前者效率的20倍,适用于少量调用;当大量调用时适合用后者,但后者第一次生成字节码时效率比JNI低5倍左右。