JVM —— 类文件结构(下)
简介
do{
自动计算pc寄存器以及从pc寄存器的位置取出操作码;
if(存在操作数){
取出操作数;
}
执行操作码所定义的操作;
}while(处理下一次循环);
操作数的数量以及长度取决于操作码,如果一个操作数的长度超过了一个字节,那么它将大端排序存储,即高位在前的字节序。限制了操作码长度为一个字节 0~255, 但是也就导致操作码个数不能超过256。
例如,如果要将一个16位长度的无符号整数使用两个无符号字节存储起来(将它们命名为byte]和byte2 ),那这个16位无符号整数的值就是: (bytel<<8) | byte2.
但是由于虚拟机操作码长度只有一个字节,所以包含了数据类型的操作码就为指令集的设计带来了很大的压力:
如果每一种数据类型相关的指令都支持Java虚拟机所有运行时数据类型的话,那指令集的数据就会超过256个了。因此虚拟机只提供了有限的指令集来支持所有的数据类型。
JVM共有9种基本类型,对于基本类型 指令在设计的时候都用一个字母缩写来指代(boolean除外)。
当然也有一些并没有明确用字母指代数据类型,比如arraylength 指令,并没有代表数据类型的特殊字符,操作数只能是一个数组类型的对象,另外还有一些,比如无条件跳转指令goto 则是与数据类型无关的。
指令-相关计算机英语词汇含义
指令-数据类型相关的指令
从上表的空白处可以看得出来:大部分数据类型相关联的指令,都没有支持整数类型 byte char short ,而且没有任何指令支持boolean类型。
因为编译器会在编译期或者运行期 将byte 和short 类型的数据 带符号扩展 为相应的int类型数据。
类似的,boolean 和char类型数据零位扩展为相应的int类型数据,在处理boolean byte short char类型的数组时,也会转换为使用对应的int类型的字节码指令来处理
按照逻辑功能进行划分
加载存储指令
加载和存储指令用于将数据在栈帧中的局部变量表和操作数栈之间来回传输。
这类指令包括如下内容:
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
存储数据的操作数栈和局部变量表主要就是由加载和存储指令进行操作的,除此之外,还有少量指令,如访问对象的字段或数组元素的指令也会向操作数栈运输数据。
运算指令
运算或算术指令用于对两个操作数栈上的值进行某种特定运算,并把结果重新存入到操作数栈顶。
算术指令分为两种:
1. 整型运算的指令。
2. 浮点型运算的指令。
无论是哪种算术指令,都使用Java虚拟机的数据类型,由于没有直接支持byte、short、char和boolean类型的算术指令,使用操作int类型的指令代替。
加法指令:iadd、ladd、fadd、dadd
减法指令:isub、lsub、fsub、dsub
乘法指令:imul、lmul、fmul、dmul
除法指令:idiv、ldiv、fdiv、ddiv
求余指令:irem、lrem、frem、drem
取反指令:ineg、lneg、fneg、dneg
位移指令:ishl、ishr、iushr、lshl、lshr、lushr
按位或指令:ior、lor
按位与指令:iand、land
按位异或指令:ixor、lxor
局部变量自增指令:iinc
比较指令:dcmpg、dcmpl、fcmpg、fcmpl、lcmp
类型转换指令
类型转换指令可以将两种不同的数值类型进行相互转换。这些转换操作一般用于实现用户代码中的显式类型转换操作,或者用来解决字节码指令集不完备的问题。
因为数据类型相关指令无法与数据类型一一对应的问题,比如byte short char boolean使用int, 所以必须要转换。
分为宽化 和 窄化含义如字面含义,存储长度的变宽或者变窄。宽化也就是常说的安全转换,不会因为超过目标类型最大值丢失信息。窄化则意味着很可能会丢失信息。
宽化指令
- int类型到long、float或者double类型 (i2l、i2f、i2d)
- long类型到float、double类型(l2f 、l2d)
- float类型到double类型(f2d)
窄化指令
- int类型到byte short char类型(i2b 、i2s 、i2c)
- long类型到int类型(l2i)
- float类型到int或者long类型(f2i 、f2l)
- double类型到int long 或者float类型(d2i 、d2l 、d2f)
对象创建与访问指令
类实例和数组都是对象。但是Java虚拟机对类实例和数组的创建使用了不同的字节码指令。
指令如下:
1. 创建类实例 : new。
2. 创建数组的指令 :
newarray 分配数据成员类型为基本数据类型的新数组
anewarray 分配数据成员类型为引用类型的新数组
multianewarray 分配新的多维数组
3. 访问类字段(static字段,或者称为类变量)和实例字段(非static字段,或者称为实例字段)的指令:
getstatic 从类中获取静态字段
putstatic 设置类中静态字段的值
getfield 从对象中获取字段值
putfield 设置对象中的字段的值
4. 把一个数组元素加载到操作数栈的指令:baload caload saload iaload laload faload daload aaload
5. 把一个操作数栈的值存储到数组元素中的指令:bastore castore sastore iastore lastore fastore dastore aastor
6. 取数组长度的指令:arraylength
7. 检查类实例或者数组类型的指令:instanceof checkcast
操作数栈管理指令
操作数栈管理指令,顾名思义就是直接用于管理操作栈的,操作数栈的直接操作主要有 出栈/复制栈顶元素 / 以及 交换栈顶元素。
如同操作应普通数据结构中的堆栈那样,java虚拟机提供了一些用于直接操作数栈的指令,包括:
1. 将操作数栈的栈顶一个或两个元素出栈:pop、pop2。
2. 复制栈顶一个或两个数值并将复制值或双份的复制值重新压入栈顶:dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2.
3. 将栈最顶端的两个数值互换:swap。
控制转移指令
控制转移指令可以让Java虚拟机有条件或者无条件的从指定的位置指令继续执行程序,而不是当前控制转移指令的下一条。
控制指令如下:
1. 条件分支
ifeq 当栈顶int类型元素 等于0时 ,跳转
ifne 当栈顶int类型元素 不等于0 时,跳转
iflt 当栈顶int类型元素 小于0 时,跳转
ifle 当栈顶int类型元素 小于等于0 时,跳转
ifgt 当栈顶int类型元素 大于0 时,跳转
ifge 当栈顶int类型元素 大于等于0 时,跳转
if_icmpeq 比较栈顶两个int类型数值的大小 ,当前者 等于 后者时,跳转
if_icmpne 比较栈顶两个int类型数值的大小 ,当前者 不等于 后者时,跳转
if_icmplt 比较栈顶两个int类型数值的大小 ,当前者 小于 后者时,跳转
if_icmple 比较栈顶两个int类型数值的大小 ,当前者 小于等于 后者时,跳转
if_icmpge 比较栈顶两个int类型数值的大小 ,当前者 大于等于 后者时,跳转
if_icmpgt 比较栈顶两个int类型数值的大小 ,当前者 大于 后者时,跳转
if_acmpeq 比较栈顶两个引用类型数值的大小 ,当前者 等于 后者时,跳转
if_acmpne 比较栈顶两个引用类型数值的大小 ,当前者 不等于 后者时,跳转
2.复合条件分支
tableswitch switch 条件跳转 case值连续
lookupswitch switch 条件跳转 case值不连续
3.无条件分支
goto 无条件跳转
goto_w 无条件跳转 宽索引
jsr SE6之前 finally字句使用 跳转到指定16位的offset,并将jsr下一条指令地址压入栈顶
jsr_w SE6之前 同上 宽索引
ret SE6之前返回由指定的局部变量所给出的指令地址(一般配合jsr jsr_w使用)
w 同局部变量的宽索引含
方法调用和返回指令
方法调用分为:实例方法,接口方法,调用父类私有实例,初始化等特殊方法,类静态方法等
以下5条指令用于方法调用:
invokevirtual指令用于调用对象的实例方法
invokeinterface指令用于调用接口方法,它会在运行时搜索由特定对象所实现的这个接口方法,并找出适合的方法进行调用。
invokespecial指令用于调用一些需要特殊处理的实例方法,包括实例初始化方法、私有方法和父类方法。
invokestatic指令用于调用类方法(static方法)
invokedynamic 调用动态链接方法
异常处理指令
Java程序中显式抛出异常的操作 throw语句,都是由athrow 指令来实现的,除了throw语句显式的抛出异常情况之外,
Java虚拟机规范还规定了许多运行时异常,会在其他Java虚拟机指令检测到异常情况时,自动抛出。
同步指令
java虚拟机可以支持方法级的同步和方法内部一段指令序列的同步,这两种同步结构都是使用管程(Monitor)来支持的。
同步一段指令集序列通常是由Java语言中的synchronized 语句块来表示的,
Java虚拟机的指令集中有monitorenter monitorexit (monitor +enter/exit)
指令小结:
加载存储指令 | 将数据在栈帧中的局部变量表和操作数栈之间来回传输。 |
运算指令 | 用于对两个操作数栈上的值进行某种特定运算,并把结果重新存入到操作数栈顶。 |
类型转换指令 | 将两种不同的数值类型进行相互转换。 |
对象创建和访问指令 | .... |
操作数栈管理指令 | 用于管理操作栈的,操作数栈的直接操作主要有 出栈/复制栈顶元素 / 以及 交换栈顶元素。 |
控制转移指令 | 让Java虚拟机有条件或者无条件的从指定的位置指令继续执行程序,而不是当前控制转移指令的下一条。条件分支、复合条件分支、无条件分支 |
方法调用和返回指令 | |
异常处理指令 | |
同步指令 |