本篇为《JVM指令分析实例》的第五篇,相关实例均使用Oracle JDK 1.8编译,并使用javap生成字节码指令清单。
前几篇传送门:
预备知识
局部变量表的变量槽(Variable Slot)
局部变量表的容量以变量槽(Variable Slot)为最小单位,虚拟机规范中并没有明确指明一个Slot应占用的内存空间大小。
每个Slot能存放一个boolean、byte、char、short、int、float、reference或returnAddress类型的数据。
reference类型表示对一个对象实例的引用,虚拟机规范没有说明它的长度及结构。
returnAddress类型目前已经很少见了,它是为字节码指令jsr、jsr_w和ret服务的,指向了一条字节码指令的地址。
对于64位的数据类型(long、double),虚拟机会以高位对齐的方式为其分配两个连续的Slot空间。
操作数栈管理指令
复制指令实例代码
package jvm.specification.se8.chapter3;
public class NextIndex {
private long index = 0;
public long nextIndex() {
return index++;
}
}
字节码指令序列
public long nextIndex():
0: aload_0 // 将第1个局部变量this压入栈顶
1: dup // 复制栈顶this并压入栈顶. 栈底到栈顶:this、this
2: getfield #12 // Field index:J. 获取实例字段index并压入栈顶,消耗栈顶的1个this. 栈底到栈顶:this、index_for_ladd
5: dup2_x1 // 复制栈顶index数值,并插入第1个this下面. 栈底到栈顶:index_for_return、this、index_for_ladd
6: lconst_1 // 将long类型常量1压入栈顶
7: ladd // 将栈顶的2个long类型数值相加,并将结果压入栈顶. 栈底到栈顶:index_for_lreturn、this、index_for_putfield
8: putfield #12 // Field index:J. 将栈顶数值赋值给实例字段index
11: lreturn
Constant pool:
#1 = Class #2 // jvm/specification/se8/chapter3/NextIndex
#2 = Utf8 jvm/specification/se8/chapter3/NextIndex
#3 = Class #4 // java/lang/Object
#4 = Utf8 java/lang/Object
#5 = Utf8 index
#6 = Utf8 J
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Methodref #3.#11 // java/lang/Object."<init>":()V
#11 = NameAndType #7:#8 // "<init>":()V
#12 = Fieldref #1.#13 // jvm/specification/se8/chapter3/NextIndex.index:J
#13 = NameAndType #5:#6 // index:J
dup2_x1指令
复制栈顶的1个或2个值,并将其插入到栈顶的2个或3个值下面。
在预备知识中,我们对局部变量的Slot做了简单说明。可以简单理解为,long类型和double类型占2个Slot,其他类型占1个Slot。
下面拆解一下指令的定义(借用局部变量的Slot概念来描述,有点不太严谨,但易于理解与记忆。)。
复制栈顶的1个或2个值:
1个可以是long类型和double类型,2个是其他类型,共2个Slot。
插入到栈顶的2个或3个值下面:
如果复制的是1个值(即栈顶是long或double,共2个Slot),那么插入到栈顶的2个值(栈顶1个long或double,下面1个其他类型,共3个Slot)下面。
如果复制的是2个值(即栈顶是2个其他类型数值,共2个Slot),那么插入到栈顶的3个值(栈顶3个都是其他类型,共3个Slot)下面。
简单理解,dup2_x1指令的作用就是将栈顶的2个Slot的值复制并插入到栈顶的3个Slot的值下面。
对于本实例,执行dup2_x1指令之前的栈结构为(栈底到栈顶):this、index。由于index为long类型,占2个Slot。this为引用类型,占1个Slot。因此,dup2_x1指令将栈顶的2个Slot的index值复制并插入到栈顶的3个Slot的this引用下面。
操作数栈之指令系数法
dup总共有6个指令,分别是dup、dup_x1、dup_x2、dup2、dup2_x1和dup2_x2。初看这些指令,容易混淆而难以理解。经过分类和找规律,可以通过"指令系数法"来理解记忆,非常简单:
- 不带_x的指令是复制栈顶数据并压入栈顶。包括两个指令,dup和dup2
- 带_x的指令是复制栈顶数据并插入栈顶以下的某个位置。共有4个指令
-
dup的系数代表要复制的Slot个数。
- dup开头的指令用于复制1个Slot的数据。例如1个int或1个reference类型数据
- dup2开头的指令用于复制2个Slot的数据。例如1个long,或2个int,或1个int+1个float类型数据
-
对于带_x的复制插入指令,只要将指令的dup和x的系数相加,结果即为需要插入的位置。因此
- dup_x1插入位置:1+1=2,即栈顶2个Slot下面。
- dup_x2插入位置:1+2=3,即栈顶3个Slot下面。
- dup2_x1插入位置:2+1=3,即栈顶3个Slot下面。
- dup2_x2插入位置:2+2=4,即栈顶4个Slot下面。
操作数栈管理指令共有9个,上面已经介绍了6个。剩下的3个用同样的方法就很容易理解了:
- pop:将栈顶的1个Slot数值出栈。例如1个short类型数值
- pop2:将栈顶的2个Slot数值出栈。例如1个double类型数值,或者2个int类型数值
- swap:交换栈顶的2个Slot数值位置。Java虚拟机没有提供交换两个64位数据类型(long、double)数值的指令。
备注:指令系数法是自己为了方便记忆起的名字
参考
《The Java Virtual Machine Specification, Java SE 8 Edition》
《Java虚拟机规范》(Java SE 8版)
《深入理解Java虚拟机 JVM高级特性与最佳实践》
转载请注明来源:http://zhanjia.iteye.com/blog/2432142
个人公众号
二进制之路
相关推荐
理解JVM指令手册对于优化Java代码、进行内存分析、理解垃圾收集机制、排查运行时错误等方面都有重要作用。通过深入学习JVM指令,开发者可以更好地理解字节码层面的运行机制,从而编写出更高效、更稳定的Java程序。...
JVM指令分为五类:操作数栈管理指令、局部变量表操作指令、控制流指令、字节码操作指令和对象及数组操作指令。这些指令共同构成了Java程序的运行基础。 2. **操作数栈管理指令** 操作数栈是JVM中存储数据的地方。...
1. **数据加载与存储指令**:如`iconst_m1`到`iconst_5`用于将-1至5的整数值直接压入操作数栈,`ldc`用于加载常量池中的基本类型或字符串,`aload`和`astore`系列用于对象引用的加载和存储。 2. **算术运算指令**:...
1. **加载和存储指令**:这类指令用于在局部变量表中加载和存储数据,例如`iload`用于从局部变量表加载一个整型值到操作数栈,`istore`则将操作数栈顶部的整型值存储回局部变量表。 2. **算术运算指令**:JVM提供了...
JVM指令由一个单字节的操作码(opcode)和可选的操作数组成。操作码定义了指令的行为,而操作数提供了指令执行所需的额外信息,如变量索引、常量池索引等。 ### 3. 字节码分析工具 为了便于查看和理解JVM指令,...
- 指令手册会列出所有这些指令,比如`iconst_5`表示将整数5压入操作数栈,`aload_0`用于将局部变量表的第一个元素(通常为this引用)加载到操作数栈。 3. **垃圾收集**: - JVM负责自动管理内存,通过垃圾收集...
例如,`iconst_1`是加载整数常量1到操作数栈的指令,它的指令码可能就是特定的8位数字。 2. 类型系统与操作数栈: JVM使用一种静态类型系统,这意味着在编译时类型就已经确定。在执行过程中,数据通过操作数栈进行...
使用javap命令可以生成JVM指令码的详细信息,包括指令码的 opcode、操作数、栈帧信息等。 通过上面的示例,我们可以了解Java JVM程序指令码实例解析的过程,包括编译Java源代码、生成.class文件和反编译.class文件...
了解JVM指令集有助于我们分析和优化Java代码,尤其是对于性能敏感的应用,理解JVM内部的运作机制可以更好地进行问题排查和性能调优。通过阅读"00-JVM指令手册.pdf",开发者可以深入理解这些概念,并应用到实际编程中...
每条JVM指令都会对操作数栈进行操作,如入栈、出栈、做算术运算等。 7. **指令集**:JVM有一套完整的指令集,包括加载/存储指令、算术逻辑指令、控制流指令、对象处理指令、类型检查和转换指令等。例如: - **a...
此外,JVM指令查询手册通常会包含详细的指令格式、操作数解释以及每条指令的执行效果,便于开发者查阅和学习。通过深入研究JVM指令,开发者可以更精确地理解Java程序在JVM中的运行过程,这对于解决性能问题、设计...
这一系列指令主要用于将基本类型的常量推送到操作数栈的顶部。例如: - `iconst_m1` 到 `iconst_5` 分别用于将-1到5的整数值推送到栈顶。 - `lconst_0` 和 `lconst_1` 用于推送long型的0和1。 - `fconst_0` 到 `...
- `iload`, `lload`, `fload`, `dload` 和 `aload`:分别用于装载int、long、float、double和引用类型的值到操作数栈。 - `iload_0`至`iload_3`,`lload_0`至`lload_3`,`fload_0`至`fload_3`,`dload_0`至`dload_...
JVM 的指令集采用了固定长度的格式,每条指令都是由一个操作码(Opcode)和零个或多个操作数组成。这种设计使得指令集既简单又易于扩展。 例如,“iload”表示加载整数到操作数栈,“anewarray”用于创建数组对象,...
堆存储对象实例,方法区存放类信息,程序计数器记录当前线程执行的指令地址,虚拟机栈保存每个方法的局部变量、操作数栈等,本地方法栈为JNI调用的本地方法服务。 3. 指令集:JVM使用一套基于栈的指令集,这些指令...
JVM执行的是字节码指令集,如`aload`(加载引用到操作数栈)、`iload`(加载整数到操作数栈)、`invokevirtual`(调用虚方法)等。模拟JVM需要理解并实现这些指令的解析和执行逻辑: 1. **解析字节码**:读取.class...
2. 数量检查:操作数栈中的数据数量需与指令相对应。比如,二元运算指令需要栈顶有两元素,多出或不足都会触发异常。 3. 运算符限制:对于算术运算、逻辑运算等,JVM会确保操作符的操作数类型正确,避免出现如对...
按照JVM规范,操作指令需要的参数是从操作数栈获得的(the operand stack)。 在JVM中,操作码可以分为不同的类别,例如: * Constants:将常量池的值或者已知的值压入操作数栈。 * Loads:将局部变量值压入操作数...
JVM8的字节码指令集包括操作数栈操作、局部变量操作、控制流、方法调用、数据类型转换等多种指令,它们构成了Java程序执行的基础。 5. 运行时数据区优化: JVM8引入了G1垃圾收集器、String去重复、堆内存并行压缩...
每当执行一条字节码指令,操作数栈可能会进行压栈或弹栈操作。 3. 动态连接(Dynamic Linking):每个栈帧都包含对所属方法的引用,以便在运行时进行动态连接。这使得Java方法调用能够灵活地查找和使用运行时常量池...