1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > 虚拟机字节码指令集

虚拟机字节码指令集

时间:2024-03-11 18:02:13

相关推荐

虚拟机字节码指令集

我们都知道 *.java 源代码经过编译器编译后会生成 *.class 文件,方法体中的代码会存放在方法表中 Code 属性中(接口和抽象类除外,没有 Code 属性).

Code 属性的结构:

attribute_name_index -> UTF8(Code)

attribute_length

info{

max_stack

max_locals

code_length

code

LineNumberTable

LocalVariableTable

}

java 编译器编译后的字节码指令就存放在 code 中.

那么下面我们重点专注下虚拟机的字节码.

我们知道指令是由操作码和操作数组成的,java 虚拟机定义的操作码由一字节表示,这样意味着最多只有256个操作码. class 文件放弃了操作数对齐,那么对于16字节的无符号数,虚拟机必须在运行时重构出该数据,类似 byte1 << 8 | byte2 这样. 所以其会牺牲一部分性能,但是好处同样是显而易见的.不需要多操作数进行补位填充,节省了空间.

java 虚拟机是面向操作数栈而不是寄存器的,所以它的指令大多数是不带操作数的. java 操作码大多都带有特定含义.

下面说下虚拟机是如何工作的:

do{

PC寄存器自动+1

根据寄存器位置读出操作码

if(判断操作码后是否有操作数) 读取操作数

执行相关功能

}while(字节码长度>0)

1.从局部变量读取数据到操作数栈

iload/lload/fload 等

2.把操作数栈的数据放回局部变量表

istore/lstore/fstore 等.

3.把常量读取到操作数栈

bipush/sipush/ldc 等

4.扩充局部变量表访问索引的指令 wide

注:iload_n 代表 iload_0 iload_1 iload_2 iload_3 这几条指令.

运算指令:用于把操作数栈上的两个数进行某种特定运算,然后把结果重新放回栈顶的操作.

1.加法

iadd/ladd/dadd 等

2.减法

isub/lsub/dsub

3.乘法

imul/lmul/dmul

4.除法

idiv/ldiv/ddiv

5.取余

irem/drem

6.or

ior/dor

7.and

iand/dand

8.取反

ineg/dneg

9.局部变量自增指令

iinc

10.比较指令

dcmpg 等

我们知道,两个很大的数据相加,最终会得到负数,这个在纯数学领域是不会的,但是在计算机中是会的(由于溢出),java 虚拟机并没有规定这个错误,只是在进行 div 或 rem 运算的时候,如果除数为 0,则会抛出 ArithmeticException 异常,其余任何整型数运算场景都不应该抛出运行时异常.

显式类型转换:i2b,i2c,l2i等.

隐式类型转换:i ->long/float/double

long -> float/double

float -> double

创建对象和访问指令

1.创建一个对象:new

2.创建数组:

① newarray 创建基本数据类型的数组

② anewarray 创建对象类型数组

③ multianewarray 创建多维数组

3.访问类字段:getfield、putfield、getstatic、putstatic

4.把一个元素数组加载到操作数栈:iaload、baload、laload 等

5.把一个操作数栈中的值存储到数组元素:iastore、lastore 等

6.取数组长度的指令:arraylength

7.检查 instanceof、checkcast

操作数栈管理指令

出栈:pop、pop2

复制栈顶元素:dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2

交换:swap

控制转移指令:就是有条件的或无条件的修改 PC 寄存器的值.

1.条件分支:ifeq、iflt 等

2.复合条件分支:tableswitch、lookupswitch

3.无条件分支:goto 等

方法调用和返回指令

(1)invokevirtual 指令用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派)

(2)invokeinterface 指令用于调用接口方法,它会在运行时搜索一个实现了这个接口方法的对象,找出合适的方法进行调用.

(3)invokespeical 指令用于调用一些需要特殊处理的实例方法,包括实例初始化方法、私有方法和父类方法.

(4)invokestatic 指令用于调用类方法(static 方法)

(5)invokedynamic 指令用于在运行时动态解析出调用点限定符所引用的方法,并执行该方法,前面4条调用指令的分派逻辑都固化在 Java 虚拟机

内部,而 invokedynamic 指令的分派逻辑是由用户所设定的引导方法决定的.

方法调用指令与数据类型无关,而方法返回指令时根据返回值的类型区分的,包括 ireturn(当返回值是 boolean、byte、char、short、int

类型时使用)、lreturn、freturn、dreturn、areturn,另外还有一条 return 指令供声明为 void 的方法、实例初始化方法以及类和接口

的类初始化方法使用.

异常处理指令

在 Java 程序中显示抛出异常的操作(throw 语句)都由 athrow 指令实现,除了用 throw 语句显式抛出异常情况之外,Java 虚拟机规范还规定

了许多运行时异常会在其他 Java 虚拟机指令检测到异常状况时自动抛出.

在 Java 虚拟机中,处理异常 (catch 语句) 不是由字节码指令来实现的(很久之前曾经使用 jsr 和 ret 指令来实现,现在已经不用了),而

是采用异常表来完成.

同步指令

Java 虚拟机可以支持方法级的同步和方法内部一段指令序列的同步,这两种同步结构都是使用管程(Monitor) 来支持的.

方法级的同步是隐式的,即无需通过字节码指令来控制,它实现在方法调用和返回操作之中. 虚拟机可以从方法常量池的方法表结构中的

ACC_SYNCHRONIZED 访问标志得知一个方法是否是同步方法. 当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置

了,如果设置了,执行线程就要求先成功持有管程,然后才能执行方法,最后当方法完成(无论是正常完成还是非正常完成)时释放管程.

在方法执行期间,执行线程持有了管程,其他任何线程都无法再获取到同一个管程. 如果一个同步方法执行期间抛出了异常,并且在方法内部无法处理

此异常,那么这个同步方法所持有的管程将在异常抛到同步方法之外时自动释放.

关于同步代码块,虚拟机中有 monitorenter 和 monitorexit 两条指令来支持 synchronized 语义. 正确实现 synchrnoized 关键字

需要 javac 编译器与 java 虚拟机两者共同协作支持.

monitorenter 和 monitorexit 指令底层是 通过lock 指令来实现的,在执行 monitorexit 之前,会将数据刷新会主存.

注:编译器必须确保无论方法通过何种方式完成,方法中调用过的每条 monitorenter 指令都必须执行器对应的 monitorexit 指令,而无论这个方法

是正常结束还是异常结束.

换句话说,monitorenter 和 monitorexit 成对出现.

公有设计和私有实现

Java 虚拟机规范描绘了 Java 虚拟机应有的共同程序存储格式:Class 文件格式以及字节码指令集.

虚拟机实现的方式主要有一下两种:

(1)将输入的 Java 虚拟机代码在加载或执行时翻译成另一种虚拟机的指令集(*.java ->.class)

(2)将输入的 Java 虚拟机代码在加载或执行时翻译成宿主机 CPU 的本地指令集(即 JIT 代码生成技术)(.java -> 目标指令集)

Class 文件结构的发展

Class 文件格式所具备的平台中立(不依赖于特定硬件及操作系统)、紧凑、稳定和可扩展的特点,是 Java 技术体系实现平台无关、语言无关两项

特性的重压支柱.

字节码指令集(参考:/cwane/p/6097838.html):

Opcode

Mnemonics

Note

Constants

0x00

nop

无动作

0x01

aconst_null

把 null 推到操作数栈

0x02

iconst_m1

把 int 常量 –1 推到操作数栈

0x03

iconst_0

把 int 常量 0 推到操作数栈

0x04

iconst_1

把 int 常量 1 推到操作数栈

0x05

iconst_2

把 int 常量 2 推到操作数栈

0x06

iconst_3

把 int 常量 3 推到操作数栈

0x07

iconst_4

把 int 常量 4 推到操作数栈

0x08

iconst_5

把 int 常量 5 推到操作数栈

0x09

lconst_0

把 long 常量 0 推到操作数栈

0x0A

lconst_1

把 long 常量 1 推到操作数栈

0x0B

fconst_0

把 float 常量 0 推到操作数栈

0x0C

fconst_1

把 float 常量 1 推到操作数栈

0x0D

fconst_2

把 float 常量 2 推到操作数栈

0x0E

dconst_0

把 double 常量 0 推到操作数栈

0x0F

dconst_1

把 double 常量 1 推到操作数栈

0x10

bipush

把单字节常量(-128~127)推到操作数栈

0x11

sipush

把 short 常量(-32768~32767)推到操作数栈

0x12

ldc

把常量池中的int,float,String型常量取出并推到操作数栈

0x13

ldc_w

把常量池中的int,float,String型常量取出并推到操作数栈(宽索引)

0x14

ldc2_w

把常量池中的long,double型常量取出并推到操作数栈(宽索引)

Loads

0x15

iload

把 int 型局部变量推到操作数栈

0x16

lload

把 long 型局部变量推到操作数栈

0x17

fload

把 float 型局部变量推到操作数栈

0x18

dload

把 double 型局部变量推到操作数栈

0x19

aload

把引用型局部变量推到操作数栈

0x1A

iload_0

把局部变量第 1 个 int 型局部变量推到操作数栈

0x1B

iload_1

把局部变量第 2 个 int 型局部变量推到操作数栈

0x1C

iload_2

把局部变量第 3 个 int 型局部变量推到操作数栈

0x1D

iload_3

把局部变量第 4 个 int 型局部变量推到操作数栈

0x1E

lload_0

把局部变量第 1 个 long 型局部变量推到操作数栈

0x1F

lload_1

把局部变量第 2 个 long 型局部变量推到操作数栈

0x20

lload_2

把局部变量第 3 个 long 型局部变量推到操作数栈

0x21

lload_3

把局部变量第 4 个 long 型局部变量推到操作数栈

0x22

fload_0

把局部变量第 1 个 float 型局部变量推到操作数栈

0x23

fload_1

把局部变量第 2 个 float 型局部变量推到操作数栈

0x24

fload_2

把局部变量第 3 个 float 型局部变量推到操作数栈

0x25

fload_3

把局部变量第 4 个 float 型局部变量推到操作数栈

0x26

dload_0

把局部变量第 1 个 double 型局部变量推到操作数栈

0x27

dload_1

把局部变量第 2 个 double 型局部变量推到操作数栈

0x28

dload_2

把局部变量第 3 个 double 型局部变量推到操作数栈

0x29

dload_3

把局部变量第 4 个 double 型局部变量推到操作数栈

0x2A

aload_0

把局部变量第 1 个引用型局部变量推到操作数栈

0x2B

aload_1

把局部变量第 2 个引用型局部变量推到操作数栈

0x2C

aload_2

把局部变量第 3 个引用型局部变量推到操作数栈

0x2D

aload_3

把局部变量第 4 个引用 型局部变量推到操作数栈

0x2E

iaload

把 int 型数组指定索引的值推到操作数栈

0x2F

laload

把 long 型数组指定索引的值推到操作数栈

0x30

faload

把 float 型数组指定索引的值推到操作数栈

0x31

daload

把 double 型数组指定索引的值推到操作数栈

0x32

aaload

把引用型数组指定索引的值推到操作数栈

0x33

baload

把 boolean或byte型数组指定索引的值推到操作数栈

0x34

caload

把 char 型数组指定索引的值推到操作数栈

0x35

saload

把 short 型数组指定索引的值推到操作数栈

Stores

0x36

istore

把栈顶 int 型数值存入指定局部变量

0x37

lstore

把栈顶 long 型数值存入指定局部变量

0x38

fstore

把栈顶 float 型数值存入指定局部变量

0x39

dstore

把栈顶 double 型数值存入指定局部变量

0x3A

astore

把栈顶引用型数值存入指定局部变量

0x3B

istore_0

把栈顶 int 型数值存入第 1 个局部变量

0x3C

istore_1

把栈顶 int 型数值存入第 2 个局部变量

0x3D

istore_2

把栈顶 int 型数值存入第 3 个局部变量

0x3E

istore_3

把栈顶 int 型数值存入第 4 个局部变量

0x3F

lstore_0

把栈顶 long 型数值存入第 1 个局部变量

0x40

lstore_1

把栈顶 long 型数值存入第 2 个局部变量

0x41

lstore_2

把栈顶 long 型数值存入第 3 个局部变量

0x42

lstore_3

把栈顶 long 型数值存入第 4 个局部变量

0x43

fstore_0

把栈顶 float 型数值存入第 1 个局部变量

0x44

fstore_1

把栈顶 float 型数值存入第 2 个局部变量

0x45

fstore_2

把栈顶 float 型数值存入第 3 个局部变量

0x46

fstore_3

把栈顶 float 型数值存入第 4 个局部变量

0x47

dstore_0

把栈顶 double 型数值存入第 1 个局部变量

0x48

dstore_1

把栈顶 double 型数值存入第 2 个局部变量

0x49

dstore_2

把栈顶 double 型数值存入第 3 个局部变量

0x4A

dstore_3

把栈顶 double 型数值存入第 4 个局部变量

0x4B

astore_0

把栈顶 引用 型数值存入第 1 个局部变量

0x4C

astore_1

把栈顶 引用 型数值存入第 2 个局部变量

0x4D

astore_2

把栈顶 引用 型数值存入第 3 个局部变量

0x4E

astore_3

把栈顶 引用 型数值存入第 4 个局部变量

0x4F

iastore

把栈顶 int 型数值存入数组指定索引位置

0x50

lastore

把栈顶 long 型数值存入数组指定索引位置

0x51

fastore

把栈顶 float 型数值存入数组指定索引位置

0x52

dastore

把栈顶 double 型数值存入数组指定索引位置

0x53

aastore

把栈顶 引用 型数值存入数组指定索引位置

0x54

bastore

把栈顶 boolean or byte 型数值存入数组指定索引位置

0x55

castore

把栈顶 char 型数值存入数组指定索引位置

0x56

sastore

把栈顶 short 型数值存入数组指定索引位置

Stack

0x57

pop

把栈顶数值弹出(非long,double数值)

0x58

pop2

把栈顶的一个long或double值弹出,或弹出2个其他类型数值

0x59

dup

复制栈顶数值并把数值入栈

0x5A

dup_x1

复制栈顶数值并把数值入栈

0x5B

dup_x2

0x5C

dup2

0x5D

dup2_x1

0x5E

dup2_x2

0x5F

swap

把栈顶端的两个数的值交换

Math

0x60

iadd

把栈顶两个 int 型数值相加并将结果入栈

0x61

ladd

把栈顶两个 long 型数值相加并将结果入栈

0x62

fadd

把栈顶两个 float 型数值相加并将结果入栈

0x63

dadd

把栈顶两个 double 型数值相加并将结果入栈

0x64

isub

把栈顶两个 int 型数值相减并将结果入栈

0x65

lsub

把栈顶两个 long 型数值相减并将结果入栈

0x66

fsub

把栈顶两个 float 型数值相减并将结果入栈

0x67

dsub

把栈顶两个 double 型数值相减并将结果入栈

0x68

imul

把栈顶两个 int 型数值相乘并将结果入栈

0x69

lmul

把栈顶两个 long 型数值相乘并将结果入栈

0x6A

fmul

把栈顶两个 float 型数值相乘并将结果入栈

0x6B

dmul

把栈顶两个 double 型数值相乘并将结果入栈

0x6C

idiv

把栈顶两个 int 型数值相除并将结果入栈

0x6D

ldiv

把栈顶两个 long 型数值相除并将结果入栈

0x6E

fdiv

把栈顶两个 float 型数值相除并将结果入栈

0x6F

ddiv

把栈顶两个 double 型数值相除并将结果入栈

0x70

irem

把栈顶两个 int 型数值模运算并将结果入栈

0x71

lrem

把栈顶两个 long 型数值模运算并将结果入栈

0x72

frem

把栈顶两个 float 型数值模运算并将结果入栈

0x73

drem

把栈顶两个 double 型数值模运算并将结果入栈

0x74

ineg

把栈顶 int 型数值取负并将结果入栈

0x75

lneg

把栈顶 long 型数值取负并将结果入栈

0x76

fneg

把栈顶 float 型数值取负并将结果入栈

0x77

dneg

把栈顶 double 型数值取负并将结果入栈

0x78

ishl

把 int 型数左移指定位数并将结果入栈

0x79

lshl

把 long 型数左移指定位数并将结果入栈

0x7A

ishr

把 int 型数右移指定位数并将结果入栈(有符号)

0x7B

lshr

把 long 型数右移指定位数并将结果入栈(有符号)

0x7C

iushr

把 int 型数右移指定位数并将结果入栈(无符号)

0x7D

lushr

把 long 型数右移指定位数并将结果入栈(无符号)

0x7E

iand

把栈顶两个 int 型数值 按位与 并将结果入栈

0x7F

land

把栈顶两个 long 型数值 按位与 并将结果入栈

0x80

ior

把栈顶两个 int 型数值 按位或 并将结果入栈

0x81

lor

把栈顶两个 long 型数值 按或与 并将结果入栈

0x82

ixor

把栈顶两个 int 型数值 按位异或 并将结果入栈

0x83

lxor

把栈顶两个 long 型数值 按位异或 并将结果入栈

0x84

iinc

把指定 int 型增加指定值

Conversions

0x85

i2l

把栈顶 int 强转 long 并入栈

0x86

i2f

把栈顶 int 强转 float 并入栈

0x87

i2d

把栈顶 int 强转 double 并入栈

0x88

l2i

把栈顶 long 强转 int 并入栈

0x89

l2f

把栈顶 long 强转 float 并入栈

0x8A

l2d

把栈顶 long 强转 double 并入栈

0x8B

f2i

把栈顶 float 强转 int 并入栈

0x8C

f2l

把栈顶 float 强转 long 并入栈

0x8D

f2d

把栈顶 float 强转 double 并入栈

0x8E

d2i

把栈顶 double 强转 int 并入栈

0x8F

d2l

把栈顶 double 强转 long 并入栈

0x90

d2f

把栈顶 double 强转 float 并入栈

0x91

i2b

把栈顶 int 强转 byte 并入栈

0x92

i2c

把栈顶 int 强转 char 并入栈

0x93

i2s

把栈顶 int 强转 short 并入栈

Comparisons

0x94

lcmp

比较栈顶两个long 型数值,把结果入栈(-1 or 0 or 1)

0x95

fcmpl

比较栈顶两个 float 型数值,把结果入栈,若有 NaN,入栈 -1

0x96

fcmpg

比较栈顶两个 float 型数值,把结果入栈,若有 NaN,入栈 1

0x97

dcmpl

比较栈顶两个 double 型数值,把结果入栈,若有 NaN,入栈 -1

0x98

dcmpg

比较栈顶两个 double 型数值,把结果入栈,若有 NaN,入栈 -1

0x99

ifeq

当栈顶 int 型数值等于0时,跳转

0x9A

ifne

当栈顶 int 型数值不等于0时,跳转

0x9B

iflt

当栈顶 int 型数值小于0时,跳转

0x9C

ifge

当栈顶 int 型数值大于等于0时,跳转

0x9D

ifgt

当栈顶 int 型数值大于0时,跳转

0x9E

ifle

当栈顶 int 型数值小于等于0时,跳转

0x9F

if_icmpeq

比较栈顶两个 int 型数值,等于0时,跳转

0xA0

if_icmpne

比较栈顶两个 int 型数值,不等于0时,跳转

0xA1

if_icmplt

比较栈顶两个 int 型数值,小于0时,跳转

0xA2

if_icmpge

比较栈顶两个 int 型数值,大于等于0时,跳转

0xA3

if_icmpgt

比较栈顶两个 int 型数值,大于0时,跳转

0xA4

if_icmple

比较栈顶两个 int 型数值,小于等于0时,跳转

0xA5

if_acmpeq

比较栈顶两个 引用 型数值,相等时跳转

0xA6

if_acmpne

比较栈顶两个 引用 型数值,不相等时跳转

Control

0xA7

goto

无条件跳转

0xA8

jsr

跳转指定16bit偏移位置,并将jsr下一条指令地址入栈

0xA9

ret

返回局部变量指定index指定位置,与jsr,jsr_w配合使用

0xAA

tableswitch

switch跳转,case连续

0xAB

lookupswitch

switch跳转,case不连续

0xAC

ireturn

从当前方法返回 int

0xAD

lreturn

从当前方法返回 long

0xAE

freturn

从当前方法返回 float

0xAF

dreturn

从当前方法返回 double

0xB0

areturn

从当前方法返回 对象引用

0xB1

return

从当前方法返回 void

References

0xB2

getstatic

获取类的静态域,并将值入栈顶

0xB3

putstatic

为类的静态域赋值

0xB4

getfield

获取类的实例域,并将值入栈顶

0xB5

putfield

为类的实例域赋值

0xB6

invokevirtual

调用实例方法

0xB7

invokespecial

调用父类构造方法,实例初始化方法,私有方法

0xB8

invokestatic

调用静态方法

0xB9

invokeinterface

调用接口方法

0xBA

invokedynamic

调用动态链接方法

0xBB

new

创建一个对象,并将引用值入栈

0xBC

newarray

创建一个原始类型数组,并将引用值入栈

0xBD

anewarray

创建一个引用类型数组,并将引用值入栈

0xBE

arraylength

获取数组长度并入栈

0xBF

athrow

抛出栈顶异常

0xC0

checkcast

检验类型转换

0xC1

instanceof

检验是否是类的实例,是1入栈,否0入栈

0xC2

monitorenter

获取对象的monitor,用于同步块或方法

0xC3

monitorexit

释放对象的monitor,用于同步块或方法

Extended

0xC4

wide

扩展访问局部变量表的索引宽度

0xC5

multianewarray

创建多维数组,并将引用值入栈

0xC6

ifnull

为 null 时跳转

0xC7

ifnonnull

非 null 时跳转

0xC8

goto_w

无条件跳转(宽索引)

0xC9

jsr_w

跳转指定32bit偏移位置,并将jsr_w下一条指令地址入栈

Reserved

0xCA

breakpoint

调试时的断点

0xFE

impdep1

用于在特定硬件中使用的语言后门

0xFF

impdep2

用于在特定硬件中使用的语言后门

参考:

① /cwane/p/6097838.html

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。