Java1.8 环境下, 我们在编写程序时会进行各种方法调用, 虚拟机在执行这些调用的时候会用到不同的字节码指令, 共有如下五种:
invokespecial: 调用私有实例方法;
invokestatic: 调用静态方法;
invokevirtual: 调用实例方法;
invokeinterface: 调用接口方法;
invokedynamic: 调用动态方法;
这里我们通过一个实例将这些方法调用的字节码指令逐个列出.
实例源码
实例共两个 java 文件, 一个是接口另一个是类, 先看接口源码, 很简单只有一个方法声明:
- package com.bolingcavalry;
- public interface Action {
- void doAction();
- }
接下来的类实现了这个接口, 而且还有自己的共有, 私有, 静态方法:
- package com.bolingcavalry;
- public class Test001 implements Action{
- private int add(int a, int b){
- return a+b;
- }
- public String getValue(int a, int b){
- return String.valueOf(add(a,b));
- }
- public static void output(String str){
- System.out.println(str);
- }
- @Override
- public void doAction() {
- System.out.println("123");
- }
- public static void main(String[] args){
- Test001 t = new Test001();
- Action a = t;
- String str = t.getValue(1,2);
- t.output(str);
- t.doAction();
- a.doAction();
- }
- public void createThread(){
- Runnable r = () -> System.out.println("123");
- }
- }
小结一下, Test001 的代码中主要的方法如下:
一个私有方法 add;
一个公有方法 getValue, 里面调用了 add 方法;
一个静态方法 output;
实现接口定义的 doAction;
一个公有方法, 里面使用了 lambda 表达式;
main 方法中, 创建对象, 调用 getValue,output,doAction;
接下来我们通过 javac 命令或者 ide 工具得到 Action.class 和 Test001.class 文件, 如果是用 Intellij IDEA, 可以先把 Test001 运行一遍, 然后在工程目录下找到 out 文件夹, 打开后里面是 production 文件夹, 再进去就能找到对应的 package 和 class 文件了, 如下图:
打开命令行, 在 Test001.class 目录下执行 javap -c Test001.class, 就可以对 class 文件进行反汇编, 得到结果如下:
- Compiled from "Test001.java"
- public class com.bolingcavalry.Test001 implements com.bolingcavalry.Action {
- public com.bolingcavalry.Test001();
- Code:
- 0: aload_0
- 1: invokespecial #1 // Method java/lang/Object."<init>":()V
- 4: return
- public java.lang.String getValue(int, int);
- Code:
- 0: aload_0
- 1: iload_1
- 2: iload_2
- 3: invokespecial #2 // Method add:(II)I
- 6: invokestatic #3 // Method java/lang/String.valueOf:(I)Ljava/lang/String;
- 9: areturn
- public static void output(java.lang.String);
- Code:
- 0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
- 3: aload_0
- 4: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
- 7: return
- public void doAction();
- Code:
- 0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
- 3: ldc #6 // String 123
- 5: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
- 8: return
- public static void main(java.lang.String[]);
- Code:
- 0: new #7 // class com/bolingcavalry/Test001
- 3: dup
- 4: invokespecial #8 // Method "<init>":()V
- 7: astore_1
- 8: aload_1
- 9: astore_2
- 10: aload_1
- 11: iconst_1
- 12: iconst_2
- 13: invokevirtual #9 // Method getValue:(II)Ljava/lang/String;
- 16: astore_3
- 17: aload_1
- 18: pop
- 19: aload_3
- 20: invokestatic #10 // Method output:(Ljava/lang/String;)V
- 23: aload_1
- 24: invokevirtual #11 // Method doAction:()V
- 27: aload_2
- 28: invokeinterface #12, 1 // InterfaceMethod com/bolingcavalry/Action.doAction:()V
- 33: return
- public void createThread();
- Code:
- 0: invokedynamic #13, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable;
- 5: astore_1
- 6: return
- }
现在我们可以对比反汇编结果来学习字节码的用法了:
invokespecial: 调用私有实例方法
getValue() 方法中调用了私有实例方法 add(int a, int b), 反编译结果如下所示, 注意编号为 3 的那一行:
- public java.lang.String getValue(int, int);
- Code:
- 0: aload_0
- 1: iload_1
- 2: iload_2
- 3: invokespecial #2 // Method add:(II)I
- 6: invokestatic #3 // Method java/lang/String.valueOf:(I)Ljava/lang/String;
- 9: areturn
可见私有实例方法的调用是通过 invokespecial 指令来实现的;
invokestatic: 调用静态方法
getValue() 方法中, 调用了静态方法 String.valueOf(), 反编译结果如下所示, 注意编号为 6 的那一行:
- public java.lang.String getValue(int, int);
- Code:
- 0: aload_0
- 1: iload_1
- 2: iload_2
- 3: invokespecial #2 // Method add:(II)I
- 6: invokestatic #3 // Method java/lang/String.valueOf:(I)Ljava/lang/String;
- 9: areturn
可见静态方法的调用是通过 invokestatic 指令来实现的;
invokevirtual: 调用实例方法
在 main() 方法中, 调用了 t.getValue(1,2) 方法, 反编译结果如下所示, 注意编号为 13 的那一行:
- public static void main(java.lang.String[]);
- Code:
- 0: new #7 // class com/bolingcavalry/Test001
- 3: dup
- 4: invokespecial #8 // Method "<init>":()V
- 7: astore_1
- 8: aload_1
- 9: astore_2
- 10: aload_1
- 11: iconst_1
- 12: iconst_2
- 13: invokevirtual #9 // Method getValue:(II)Ljava/lang/String;
- 16: astore_3
- 17: aload_1
- 18: pop
- 19: aload_3
- 20: invokestatic #10 // Method output:(Ljava/lang/String;)V
- 23: aload_1
- 24: invokevirtual #11 // Method doAction:()V
- 27: aload_2
- 28: invokeinterface #12, 1 // InterfaceMethod com/bolingcavalry/Action.doAction:()V
- 33: return
- }
可见调用一个实例的方法的时候, 通过 invokevirtual 指令来实现的;
invokeinterface: 调用接口方法
在 main() 方法中, 我们声明了接口 Action a, 然后调用了 a.doAction(), 反编译结果如下所示, 注意编号为 28 的那一行:
- public static void main(java.lang.String[]);
- Code:
- 0: new #7 // class com/bolingcavalry/Test001
- 3: dup
- 4: invokespecial #8 // Method "<init>":()V
- 7: astore_1
- 8: aload_1
- 9: astore_2
- 10: aload_1
- 11: iconst_1
- 12: iconst_2
- 13: invokevirtual #9 // Method getValue:(II)Ljava/lang/String;
- 16: astore_3
- 17: aload_1
- 18: pop
- 19: aload_3
- 20: invokestatic #10 // Method output:(Ljava/lang/String;)V
- 23: aload_1
- 24: invokevirtual #11 // Method doAction:()V
- 27: aload_2
- 28: invokeinterface #12, 1 // InterfaceMethod com/bolingcavalry/Action.doAction:()V
- 33: return
- }
可见调用一个接口的方法是通过 invokeinterface 指令来实现的;
其实 t.doAction() 和 a.doAction() 最终都是调用 Test001 的实例的 doAction, 但是 t 的声明是类, a 的声明是接口, 所以两者的调用指令是不同的;
invokedynamic: 调用动态方法
在 main() 方法中, 我们声明了一个 lambda() -> System.out.println("123"), 反编译的结果如下:
- 0: invokedynamic #13, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable;
- 5: astore_1
- 6: return
可见 lambda 表达式对应的实际上是一个 invokedynamic 调用, 具体的调用内容, 可以用 Bytecode viewer 这个工具来打开 Test001.class 再研究, 由于反编译后得到 invokedynamic 的操作数是 #13, 我们先去常量池看看 13 对应的内容:
是个 Name and type 和 Bootstrap method, 再细看 Bootstrap method 的操作数, 如下图:
是个 MethodHandler 的引用, 指向了用户实现的 lambda 方法;
以上就是五种方法调用的字节码指令的简单介绍, 实际上每个指令背后都对应着更复杂的调用和操作, 有兴趣的读者可以通过虚拟机相关的书籍和资料继续深入学习.
来源: https://www.cnblogs.com/bolingcavalry/p/11582357.html