Java方法调用的字节码指令学习
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方法;
以上就是五种方法调用的字节码指令的简单介绍,实际上每个指令背后都对应着更复杂的调用和操作,有兴趣的读者可以通过虚拟机相关的书籍和资料继续深入学习。
欢迎关注我的公众号:程序员欣宸
Java方法调用的字节码指令学习的更多相关文章
- java虚拟机(十四)--字节码指令
字节码指令其实是很重要的,在之前学习String等内容,深入到字节码层面很容易找到答案,而不是只是在网上寻找答案,还有可能是错误的. PS:本文基于jdk1.8 首先写个简单的类: public cl ...
- JAVA方法调用中的解析与分派
JAVA方法调用中的解析与分派 本文算是<深入理解JVM>的读书笔记,参考书中的相关代码示例,从字节码指令角度看看解析与分派的区别. 方法调用,其实就是要回答一个问题:JVM在执行一个方法 ...
- 大话+图说:Java字节码指令——只为让你懂
前言 随着Java开发技术不断被推到新的高度,对于Java程序员来讲越来越需要具备对更深入的基础性技术的理解,比如Java字节码指令.不然,可能很难深入理解一些时下的新框架.新技术,盲目一味追新也会越 ...
- JVM学习笔记——字节码指令
JVM学习笔记——字节码指令 字节码 0与 1是计算机仅能识别的信号,经过0和1的不同组合产生了数字之上的操作.另外,通过不同的组合亦产生了各种字符.同样,可以通过不同的组合产生不同的机器指令.在不同 ...
- JVM 字节码指令手册 - 查看 Java 字节码
JVM 字节码指令手册 - 查看 Java 字节码 jdk 进行的编译生成的 .class 是 16 进制数据文件,不利于学习分析.通过下命令 javap -c Demo.class > Dem ...
- JVM学习第三天(JVM的执行子系统)之字节码指令
早上看了Class类文件结构,晚上继续来看字节码指令,毕竟谁也不是一步登天的(说白了还是穷); 字节码指令 Java虚拟机的指令由一个字节长度的.代表着某种特定操作含义的数字(称为操作码,Opcode ...
- [四] java虚拟机JVM编译器编译代码简介 字节码指令实例 代码到底编译成了什么形式
前言简介 前文已经对虚拟机进行过了简单的介绍,并且也对class文件结构,以及字节码指令进行了详尽的说明 想要了解JVM的运行机制,以及如何优化你的代码,你还需要了解一下,java编译器到底是 ...
- 深入理解java虚拟机(六)字节码指令简介
Java虚拟机指令是由(占用一个字节长度.代表某种特定操作含义的数字)操作码Opcode,以及跟随在其后的零至多个代表此操作所需参数的称为操作数 Operands 构成的.由于Java虚拟机是面向操作 ...
- 深入了解java虚拟机(JVM) 第十章 字节码指令
一.字节码指令的含义 Java字节码指令由一个字节长度的,代表某种特定操作含义的数字(操作码)以及其后的零至多个代表此操作所需参数(操作数).此外字节码指令是面向操作数栈的,这里操作数栈在功能上对应实 ...
随机推荐
- Flink的TaskManager启动(源码分析)
通过启动脚本已经找到了TaskManager 的启动类org.apache.flink.runtime.taskexecutor.TaskManagerRunner 来看一下它的main方法中 最后被 ...
- 阿里分布式事务seata入门(采坑)
1. 阿里分布式事务seata入门(采坑) 1.1. 前言 seata是feascar改名而来,这是阿里在19年年初开源出来的分布式事务框架,当初刚出来的时候就想研究下了,一直拖到了现在,目前是0.8 ...
- 并查集(不相交集合)详解与java实现
目录 认识并查集 并查集解析 基本思想 如何查看a,b是否在一个集合? a,b合并,究竟是a的祖先合并在b的祖先上,还是b的祖先合并在a上? 其他路径压缩? 代码实现 结语 @(文章目录) 认识并查集 ...
- Postman系列五:Postman中电商网站cookie、token检验与参数传递实战
一:Postman中电商网站cookie实战 Postman接口请求使用cookie两种方式: 1.直接在header(头域)中添加cookie,适用于已知请求cookie头域的情况 2.使用Post ...
- Leetcode之深度优先搜索(DFS)专题-1020. 飞地的数量(Number of Enclaves)
Leetcode之深度优先搜索(DFS)专题-1020. 飞地的数量(Number of Enclaves) 深度优先搜索的解题详细介绍,点击 给出一个二维数组 A,每个单元格为 0(代表海)或 1( ...
- 简明Python教程-函数联系笔记
1.实参与形参 在定义函数时给定的名称称作"形参",再调用函数时你所提供给函数的值称作“实参” 2.局部变量 所有变量的作用域是它们被定义的块,从定义它们的名字的定义点开始. 3. ...
- asio kcp源码分析
asio kcp代码走读 (1)kcp_client_wrap类 a 提供方法接口如下: send_msg kcp_client_.send_msg(msg); stop //等待工作线程退出 set ...
- codeforce#483div2D-XOR-pyramid+DP
题意:求给定区间中最大的连续异或和: 思路:DP的思想,先dp求出每个区间的异或和,再dp更新成当前这个dp[i][j]和dp[i-1][j].dp[i-1][j+1]中的最大值: 这样可以保证是同一 ...
- hdu 5037 Frog(贪心)
题目链接:http://acm.split.hdu.edu.cn/showproblem.php?pid=5037 题解:为了让放的石头有意义肯定是没l+1的距离放2个也就是说假设现在位置为pos那么 ...
- JOBDU 1027 欧拉回路
题目1027:欧拉回路 时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:3620 解决:1847 题目描述: 欧拉回路是指不令笔离开纸面,可画过图中每条边仅一次,且可以回到起点的一条 ...