方法执行调用
1. 方法执行
1.1 字节码指令集集
Java虚拟机的指令由⼀个字节⻓度的
- 代表着某种特定操作含义的数字(称为操作码,Opcode)
- 跟随其后的零⾄多个代表此操作所需参数(称为操作数,Operands)⽽构成。
Opcode+操作数
- iconst_0 操作码
- bipush 10 操作码+操作数
常见指令集:








1.2 基本数据类型
1、除了long和double类型外,每个变ᰁ都占局部变ᰁ区中的⼀个变ᰁ槽(slot),⽽long及double会占⽤ 两个连续的变ᰁ槽。
2、⼤多数对于boolean、byte、short和char类型数据的操作,都使⽤相应的int类型作为运算类型。


1)、加载和存储指令
1. 将⼀个【局部变量表】加载到【操作数栈】:
iload、iload_<n>、lload、lload_<n>、fload、fload_<n>、dload、dload_<n>、aload、aload_<n>
2)将⼀个数值从【操作数栈】存储到【局部变量表】:
istore、istore_<n>、lstore、lstore_<n>、fstore、fstore_<n>、dstore、dstore_<n>、astore、astore_<n>
3、将⼀个【常量】加载到【操作数栈】:
bipush、sipush、ldc、ldc_w、ldc2_w、aconst_null、iconst_m1、iconst_<i>、lconst_<l>、fconst_<f>、dconst_<d>
4. 扩充局部变ᰁ表的访问索引的指令
wide_<n>:_0、_1、_2、_3,
存储数据的操作数栈和局部变ᰁ表主要就是由加载和存储指令进⾏操作,除此之外,还有少量指令,如访问对象的字段或数组元素的指令也会向操作数栈传输数据。
2)const系列(⼩数值)
该系列命令主要负责把【简单的数值类型】送到【操作数栈栈顶】。该系列命令不带参数。注意只把简 单的数值类型送到栈顶时,才使⽤如下的命令。
⽐如对应int型,该⽅式只能把-1,0,1,2,3,4,5(分别采⽤iconst_m1,iconst_0, iconst_1, iconst_2, iconst_3, iconst_4, iconst_5)送到栈顶。 对于int型,其他的数值请使⽤push系列命令(⽐如bipush)。

3)、push系列(中数值)
该系列命令负责把⼀个【整形数字(⻓度⽐较⼩)】送到到【操作数栈栈顶】。该系列命令【有⼀个参 数】,⽤于指定要送到栈顶的数字。
注意该系列命令只能操作⼀定范围内的整形数值,超出该范围的使⽤将使⽤【ldc命令】系列。
指令码 助记符 说明

4)ldc系列(⼤数值或字符串常量)
该系列命令负责把【⻓度较⻓的数值常ᰁ】或【String常ᰁ值】从【常ᰁ池中】推送⾄【操作数栈栈 顶】。该命令后⾯需要给⼀个表示常量在常量池中位置(编号)的参数,
对于const系列命令和push系列命令操作范围之外的数值类型常ᰁ,都放在常ᰁ池中。 另外,所有不是通过new创建的String都是放在常量池中的。

1.3 load系列
1、load系列A
该系列命令负责把【本地变ᰁ表中的值】送到【操作数栈栈顶】。这⾥的本地变ᰁ不仅可以是数值类 型,还可以是引⽤类型。
对于前四个本地变量可以采⽤iload_0,iload_1,iload_2,iload_3(它们分别表示第0,1, 2,3个整形变量)这种不带参数的简化命令形式。
对于第4以上的本地变量将使⽤iload命令这种形式,在它后⾯给⼀参数,以表示是对第⼏个 (从0开始)本类型的本地变量进⾏操作。对本地变量所进⾏的编号,是对所有类型的本地变量进⾏的(并不按照类型分类。
对于⾮静态函数(虚⽅法,实例⽅法),第⼀变量是this,即其对应的操作是aload_0。 还有函数传⼊参数也算本地变量,在进⾏编号时,它是先于函数体的本地变量的。



2. load系列B
该系列命令负责把数组的某项送到栈顶。该命令根据栈⾥内容来确定对哪个数组的哪项进⾏操作。

1.4 store系列
2. 方法调用
相关知识点
1. 私有⽅法(类和私有⽅法绑死了)
1. ⾮虚⽅法
2. invokespecial
3. 静态绑定
2. 构造⽅法(类和构造⽅法绑死了)
1. ⾮虚⽅法
2. invokespecial
3. 静态绑定
3. 静态⽅法(类和静态⽅法绑死了)
1. ⾮虚⽅法
2. invokestatic
3. 静态绑定
4. 成员⽅法(类和成员⽅法绑死了吗?思考多态)
1. 虚⽅法
2. invokevirtual
3. 动态绑定
5. 接⼝⽅法(接⼝实现类和接⼝⽅法绑死了吗?)
1. 虚⽅法
2. invokeinterface
3. 动态绑定
常⻅⽅法调⽤类型
1. 私有⽅法(类和私有⽅法绑死了)
2. 构造⽅法(类和构造⽅法绑死了)
3. 静态⽅法(类和静态⽅法绑死了)
StringUtils.isNotBlank();
4. 成员⽅法(类和成员⽅法绑死了吗?思考多态)
Father f = new Son(); f.eat(); // 调⽤的是Father还是Son的eat⽅法呢?只有在运⾏的时候才能确定调⽤是哪个对象的⽅ 法
5. 接⼝⽅法(接⼝实现类和接⼝⽅法绑死了吗?)
UserService service = new UserService1(); UserService service = new UserService2(); service.save();

虚⽅法和⾮虚⽅法
如果在编译期就可以确定要调⽤的⽬标⽅法,那么该⽬标⽅法就被称为⾮虚⽅法(静态绑定)。反之都 是虚⽅法(动态绑定)。
⾮虚⽅法包括:静态⽅法、私有⽅法、final⽅法、实例构造器、⽗类⽅法(super())。非虚方法不能被子类继承和覆写。
所有⾮私有实例⽅法被调⽤-->编译-->invokevirtual指令.
接⼝⽅法调⽤-->编译-->invokeinterface指令.
这两种指令,均属于Java虚拟机中的虚⽅法调⽤。
⽅法调⽤指令
我们再从JVM层⾯分析下,JVM⾥⾯是通过哪⾥指令来实现⽅法的调⽤的:
--⾮虚⽅法调⽤指令
- invokestatic:调⽤静态⽅法
- invokespecial:调⽤⾮静态私有⽅法、构造⽅法(包括super)
--虚⽅法调⽤指令
- invokeinterface:调⽤接⼝⽅法([多态]())
- invokevirtual:调⽤⾮静态⾮私有⽅法([多态]())
- invokedynamic:动态调⽤(Java7引⼊的,第⼀次⽤却是在Java8中,⽤在了Lambda表达式和默认⽅ 法中,它允许调⽤任意类中的同名⽅法,注意是任意类,和重载重写不同)(动态 ≠ 多态)
具体来说,Java字节码指令中与调⽤相关的指令共有五种:
- invokevirtual :
调⽤⾮静态⾮私有⽅法(多态) ⽤于调⽤对象的实例⽅法,根据对象的实际类型进⾏分派(虚⽅法分派),这也是Java语⾔中最常 ⻅的⽅法分派⽅式。 - invokeinterface :
调⽤接⼝⽅法(多态) ⽤于调⽤接⼝⽅法,它会在运⾏时搜索⼀个实现了这个接⼝⽅法的对象,找出适合的⽅法进⾏调 ⽤。 - invokespecial :
调⽤⾮静态私有⽅法、构造⽅法(包括super) ⽤于调⽤⼀些需要特殊处理的实例⽅法,包括实例初始化(<init>)⽅法、私有⽅法和⽗类⽅ 法。 - invokestatic :
调⽤静态⽅法(static⽅法)。 - invokedynamic :
⽤于在运⾏时动态解析出调⽤点限定符所引⽤的⽅法,并执⾏该⽅法,前⾯4条调⽤指令的分派逻 辑都固化在Java虚拟机内部,⽽invokedynamic指令的分派逻辑是由⽤户所设定的引导⽅法决定 的。
静态绑定和动态绑定
Java 虚拟机识别⽅法的关键在于类名、⽅法名以及⽅法描述符(method descriptor)。
JVM识别⽅法的关键在于【类名+⽅法名+⽅法描述符(参数类型、返回类型)】,这其实就是⽅法的完整表 示⽅式(符号引⽤)

⽅法和类关联起来,叫做⽅法的绑定,绑定分为静态绑定和动态绑定。
在JVM中,将符号引⽤替换为直接引⽤(⽬标⽅法的内存地址)的处理逻辑,与⽅法的绑定机制有关系。
Java语⾔具有继承和多态的特性,所以⾃然⽽然的也就具备以下两种绑定⽅式:静态绑定和动态绑定。
当程序中调⽤了某⼀个⽅法时:
- 如果在编译期(直接可以根据编译类型确定内存中的⽬标⽅法)就可以确定⽬标⽅法的话,并且在运⾏期间不可变,这其实就是⼀种静态绑定机制,也叫静态链接,也叫静态分派,也叫前期绑定。
静态⽅法
私有⽅法
final⽅法
this()
super()
2. 如果在运⾏期(不能根据编译类型确定内存中的⽬标⽅法,需要根据对象类型去确定内存中的⽬标⽅法)间才能根据对象类型确定⽬标⽅法的话,这是动态绑定机制,也叫动态链接、也叫动态分派,也叫后期绑定。
静态绑定
Java虚拟机中的静态绑定(static binding)指的是在类加载时的解析阶段便能够直接识别⽬标⽅法的情 况。 在Java中,final、private、static修饰的⽅法以及构造函数都是静态绑定的,不需程序运⾏,不需具体的 实例对象就可以知道这个⽅法的具体内容。
动态绑定
⽽动态绑定(dynamic binding)则指的是需要在运⾏过程中根据调⽤者的动态类型来识别⽬标⽅法的情况.

浙公网安备 33010602011771号