Java虚拟机的指令由一个字节长度的、代表着某种特定操作含义的操作码以及跟随其后的零至多个代表此操作所需参数的操作数所构成。虚拟机中许多指令并不包含操作数,只有一个操作码。
Java虚拟机限制操作码的长度为1个字节,因此最多只能有256个指令。
指令格式
以下指令格式,是基于Oracle JDK编译后,通过javap工具生成的指令描述格式。
<index> <opcode> [<operand1> [<operand2>...]] [<comment>]
<index>
指令操作码在方法字节码指令数组中的索引,也可以认为是相对于方法起始处的字节偏移量。其中,指令数组指方法对应的Code属性的code[]数组,该数组用于存放方法的字节码指令。
该索引可以作为控制转移指令的跳转目标。例如,goto 8指令表示跳转到索引为8的指令上继续执行。
<opcode>
指令的操作码助记符。例如,iconst_0、istore_1、iload_1和return等。
<operandN>
指令操作数,一条指令可以有0至多个操作数。例如,iconst_0没有操作数,bipush有1个操作数,iinc有2个操作数。
<comment>
指令行尾的注释。注释内容通常以//开始。
每一行中,表示运行时常量池索引的操作数前,会有一个井号。在指令后的注释中,会带有对这个操作数的描述,例如:
1: invokespecial #8 // Method java/lang/Object."<init>":()V
10: ldc2_w #19 // double 100.0d
实例分析
以下实例均使用JDK 1.8编译,并使用javap生成字节码指令清单。
代码1
void spin() {
int i;
for (i = 0; i < 100; i++) {
; // Loop body is empty
}
}
字节码指令序列
iinc用于实现局部变量的自增操作。在所有字节码指令中,只有该指令可直接用于操作局部变量。
对于非-1至5的int类型常量(对应指令iconst_N),使用bipush来将单字节常量值推至栈顶。
JVM对int类型提供了比较和跳转相结合的if指令,例如该例子中的if_icmplt指令。而对于long、float和double,则需要先通过各自的cmp比较指令计算出int类型结果,再结合int类型的if指令判断后再进行跳转。
代码2
void dspin() {
double i;
for (i = 0.0; i < 100.0; i++) {
; // Loop body is empty
}
}
字节码指令序列
其中,double类型占用局部变量的2个Slot,局部变量索引号从0开始,因此dstore_1对应的局部变量索引为1和2。
由于iinc只针对int类型进行自增操作,JVM并没有提供相应的指令来操作double类型。因此,需要借助dadd来实现double类型的自增操作。
同样,以if开头的比较跳转指令,都只用于int类型。但JVM另外提供了dcmpg、dcmpl来比较两个double类型数值的大小,然后将比较结果(1,0,-1)压入栈顶。最后,再使用int类型的if判断指令来进行判断跳转。
dcmpg与dcmpl的区别仅在于,当比较的其中一个值为NaN时,dcmpg将1压入栈顶,而dcmpl将-1压入栈顶。
ldc相关指令都是将常量值从常量池中推至栈顶。
代码3
void sspin() {
short i;
for (i = 0; i < 100; i++) {
; // Loop body is empty
}
}
字节码指令序列
short类型同样需要通过多条指令来实现i++操作,对应于索引号为5至9的指令。首先,使用iadd实现2个int类型数值相加,再使用i2s指令将int类型结果强制转换为short类型,最后使用istore_1指令将结果存回局部变量i。
对于byte、char和short类型数据,JVM并未提供像int类型一样丰富的直接操作指令。然而,由于byte、char和short类型数据都可以自动宽化转换为int类型,因此均可通过int类型的指令来操作。唯一额外的代价是要将操作结果截短至它们的有效范围内。
参考
《Java虚拟机规范》(Java SE 8版)
《深入理解Java虚拟机 JVM高级特性与最佳实践》
转载请注明来源:http://zhanjia.iteye.com/blog/2430731
个人公众号
二进制之路
相关推荐
7. **循环指令**:`fori`、`tableswitch`和`lookupswitch`等指令用于实现各种循环结构。 8. **异常处理指令**:`athrow`用于抛出异常,`catch`和`finally`配合使用来定义异常处理逻辑。 9. **多线程指令**:`...
- 局部变量编号:局部变量的编号不是按照类型分类,而是连续编号,`this`引用在非静态方法中被视为第一个局部变量(`aload_0`)。 - 代码执行流程:JVM指令能够改变程序的执行流程,可以实现方法调用、循环、条件...
1. **加载和存储指令**:这类指令用于在局部变量表中加载和存储数据,例如`iload`用于从局部变量表加载一个整型值到操作数栈,`istore`则将操作数栈顶部的整型值存储回局部变量表。 2. **算术运算指令**:JVM提供了...
3. **局部变量操作**:`iload`和`lload`等指令从局部变量表中加载int、long等类型的值到操作栈。这些指令有针对不同变量位置的变种,例如`iload_0`表示从局部变量0加载int值。对于数组,`iaload`和`laload`等指令...
- **局部变量表操作指令**:如`iload`用于将局部变量表中的整数值加载到操作数栈,`istore`则将栈顶元素存储回局部变量表。 - **算术运算指令**:如`iadd`用于执行整数加法,`fdiv`用于执行浮点数除法。 - **类型...
- 指令手册会列出所有这些指令,比如`iconst_5`表示将整数5压入操作数栈,`aload_0`用于将局部变量表的第一个元素(通常为this引用)加载到操作数栈。 3. **垃圾收集**: - JVM负责自动管理内存,通过垃圾收集...
1. **数据操作指令**:如`iconst`系列用于加载常量整数值,`iload`系列用于从局部变量表加载整数,`iadd`进行整数加法运算,`imul`进行乘法运算等,它们构成了JVM处理计算的基础。 2. **控制流指令**:如`goto`用于...
- `iinc`:增加一个int类型的局部变量的值。 - `getstatic`:从类的静态字段中获取值。 - `invokevirtual`:调用对象的实例方法。 了解JVM指令集对于理解字节码的执行流程、性能优化、以及调试Java程序都非常...
例如,`iload`指令用于从局部变量表加载int类型的变量到操作数栈,而`fload`则用于加载float类型。 3. 控制流指令: JVM指令还包括控制流程指令,如`if-eq`用于比较栈顶两个元素是否相等,如果相等则跳转到指定的...
3. **局部变量表**:每个方法都有一个局部变量表,用于存储方法参数和局部变量。这是JVM处理方法调用的核心部分。 4. **指令分类**: - **数据操作指令**:如`iconst`(加载整数值到操作数栈)、`iadd`(加法运算...
7. **指令集**:JVM有一套完整的指令集,包括加载/存储指令、算术逻辑指令、控制流指令、对象处理指令、类型检查和转换指令等。例如: - **aload_0**: 加载局部变量表中的第一个引用类型值到操作数栈。 - **iadd**...
栈中存储局部变量(包括基本类型和对象引用)、方法参数、Stack Frame。当变量超出作用域,JVM会自动释放栈内存。栈内存分配快速,但大小和生命周期必须在编译时确定。 - **堆(Heap)**:存储对象实例和数组。所有...
当从`main()`函数中调用`Min()`函数时,JVM会创建一个新的栈帧来保存局部变量和参数,并跳转到相应的字节码位置继续执行。 ##### 访问Min()函数中的参数 由于JVM采用栈式架构,访问参数通常涉及从栈顶弹出值并加载...
当我们创建一个字符串字面量,如`String s = "haha"`,JVM执行的虚拟机指令涉及到常量池。`ldc`指令从常量池中获取指定索引的`CONSTANT_String_info`条目,这里包含的是字符串"haha"的引用。然后,`astore_1`指令将...
- **栈内存(Stack)**:每个线程都有自己的栈空间,用于存储局部变量、方法调用栈帧等。栈内存的空间相对较小,但访问速度快。 - **方法区(Method Area)**:也称为永久代,用于存储类的信息、静态变量、常量池等...
3. 栈内存:每个线程都有一个独立的栈,用于存储方法调用时的局部变量、操作数栈、动态链接及方法出口等信息。栈帧随着方法的调用和返回而创建和销毁。 4. 本地方法栈:与Java方法栈类似,但服务于Java虚拟机的本地...
- 当JVM遇到一个字节码指令时,它会根据指令的性质执行相应的操作,比如从常量池加载数据,或者从局部变量表中读取和存储值。 - 操作数栈在执行过程中动态变化,指令会将值压入栈,然后进行计算,结果再存回栈或...
方法栈是 Java 方法执行的内存模型,每个方法在执行时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。PC 寄存器用于存储待执行指令的地址,以便线程恢复和跳转。Java 堆是 JVM 用于存放...
- 运行时数据区还包括栈帧,每个方法调用都会创建一个栈帧,存储局部变量表、操作数栈、动态链接、方法出口等信息。 7. **类的实例化**: - 使用 `new` 关键字创建对象时,JVM会执行实例初始化步骤,包括分配内存...