java字节码基本指令_java字节码指令的工作流程

字节代码指令

字节代码指令由一个标识该指令的操作码和固定数目的参数组成:

操作码是一个无符号字节值——即字节代码名,由助记符号标识。例如,操作码 0 用助记符号 NOP 表示,对应于不做任何操作的指令。

参数是静态值,确定了精确的指令行为。它们紧跟在操作码之后给出。比如 GOTO 标记指令(其操作码的值为 167)以一个指明下一条待执行指令的标记作为参数标记。不要将指令参数与指令操作数相混淆:参数值是静态已知的,存储在编译后的代码中,而操作数值来自操作数栈,只有到运行时才能知道。

字节代码指令可以分为两类:一小组指令,设计用来在局部变量和操作数栈之间传送值;其他一些指令仅用于操作数栈:它们从栈中弹出一些值,根据这些值计算一个结果,并将它压回栈中。

ILOAD, LLOAD, FLOAD, DLOAD 和 ALOAD 指令读取一个局部变量,并将它的值压到操作数栈中。它们的参数是必须读取的局部变量的索引 i。ILOAD 用于加载一个 boolean、byte、

char、short 或int 局部变量。LLOAD、FLOAD 和DLOAD 分别用于加载long、float 或double 值。(LLOAD 和 DLOAD 实际加载两个槽 i 和 i+1)。最后,ALOAD 用于加载任意非基元值,即对象和数组引用。与之对应,ISTORE、LSTORE、FSTORE、DSTORE 和 ASTORE 指令从操作数栈中弹出一个值,并将它存储在由其索引 i 指定的局部变量中。

可以看到,xLOAD 和 xSTORE 指令被赋入了类型(事实上,下面将要看出,几乎所有指令都被赋予了类型)。它用于确保不会执行非法转换。实际上,将一个值存储在局部变量中,然后再以不同类型加载它,是非法的。例如,ISTORE 1 ALOAD 1 序列是非法的——它允许将一个

任意内存位置存储在局部变量 1 中,并将这个地址转换为对象引用!但是,如果向一个局部变量中存储一个值,而这个值的类型不同于该局部变量中存储的当前值,却是完全合法的。这意味着一个局部变量的类型,即这个局部变量中所存值的类型可以在方法执行期间发生变化。

上面已经说过,所有其他字节代码指令都仅对操作数栈有效。它们可以划分为以下类别(见附件 A.1):栈    这些指令用于处理栈上的值:POP 弹出栈顶部的值,DUP 压入顶部栈值的一个副本,SWAP 弹出两个值,并按逆序压入它们,等等。

常量   这些指令在操作数栈压入一个常量值:ACONST_NULL 压入 null,ICONST_0 压入int 值 0,FCONST_0 压入 0f,DCONST_0 压入 0d,BIPUSH b 压入字节值 b,SIPUSHs 压入 short 值 s,LDC cst 压入任意 int、float、long、double、String 或 class①常量 cst,等等。

算术与逻辑   这些指令从操作数栈弹出数值,合并它们,并将结果压入栈中。它们没有任何参数。xADD、xSUB、xMUL、xDIV 和 xREM 对应于+、-、*、/和%运算,其中 x 为 I、L、F 或 D 之一。类似地,还有其他对应于<>、>>>、|、&和^运算的指令,用于处理 int 和 long 值。

类型变换 这些指令从栈中弹出一个值,将其转换为另一类型,并将结果压入栈中。它们对应于 Java 中的类型转换表达式。I2F, F2D, L2D 等将数值由一种数值类型转换为另一种类型。CHECKCAST t 将一个引用值转换为类型 t。对象 这些指令用于创建对象、锁定它们、检测它们的类型,等等。例如,NEW type 指令将一个 type 类型的新对象压入栈中(其中 type 是一个内部名)。

字段  这些指令读或写一个字段的值。GETFIELD owner name desc 弹出一个对象引用,并压和其 name 字段中的值。PUTFIELD owner name desc 弹出一个值和一个对象引用,并将这个值存储在它的 name 字段中。在这两种情况下,该对象都必须是 owner 类型,它的字段必须为 desc 类型。GETSTATIC 和 PUTSTATIC 是类似指令,但用于静态字段。

方法    这些指令调用一个方法或一个构造器。它们弹出值的个数等于其方法参数个数加 1(用于目标对象),并压回方法调用的结果。INVOKEVIRTUAL owner name desc 调用在类 owner 中定义的 name 方法,其方法描述符为 desc。INVOKESTATIC 用于静态方法,INVOKESPECIAL 用于私有方法和构造器,INVOKEINTERFACE 用于接口中定义的方法。最后,对于 Java 7 中的类,INVOKEDYNAMIC 用于新动态方法调用机制。

数组 这些指令用于读写数组中的值。xALOAD 指令弹出一个索引和一个数组,并压入此索引处数组元素的值。xASTORE 指令弹出一个值、一个索引和一个数组,并将这个值存储在该数组的这一索引处。这里的 x 可以是 I、L、F、D 或 A,还可以是 B、C 或 S。

跳转   这些指令无条件地或者在某一条件为真时跳转到一条任意指令。它们用于编译 if、for、do、while、break 和 continue 指令。例如,IFEQ label 从栈中弹出一个int 值,如果这个值为 0,则跳转到由这个 label 指定的指令处(否则,正常执行下一条指令)。还有许多其他跳转指令,比如  IFNE 或  IFGE。最后,TABLESWITCH 和① 对应于 identifier.class Java 语法。LOOKUPSWITCH 对应于 switch Java 指令。

返回          最后,xRETURN 和 RETURN 指令用于终止一个方法的执行,并将其结果返回给调用者。RETURN 用于返回 void 的方法,xRETURN 用于其他方法。

示例

让我们看一些基本示例,具体体会一下字节代码指令是如何工作的。考虑下面的 bean 类:

这个类用javap命令反编译为字节码文件为:

getF 方法的字节代码为:

第一条指令读取局部变量 0(它在为这个方法调用创建帧期间被初始化为 this),并将这个值压入操作数栈中。第二个指令从栈中弹出这个值,即 this,并将这个对象的 f 字段压入栈中, 即 this.f。最后一条指令从栈中弹出这个值,并将其返回给调用者。图中给出了这个方法执行帧的持续状态。

4fd08b2303826dbc43525c0ed627cfc5.png

图 3.2 getF 方法的持续帧状态:

a) 初始状态,

b) 在 ALOAD 0 之后,

c) 在 GETFIELD 之后

setF 方法的字节代码:

和之前一样,第一条指令将 this 压入操作数栈。第二条指令压入局部变量 1,在为这个方法调用创建帧期间,以 f 参数初始化该变量。第三条指令弹出这两个值,并将 int 值存储在被引用对象的 f 字段中,即存储在 this.f 中。最后一条指令在源代码中是隐式的,但在编译后的代码中却是强制的,销毁当前执行帧,并返回调用者。这个方法执行帧的持续状态如图 所示。

f01b4fa735d54ee1ce6e04e2bf4a0733.png

图 3.3 setF 方法的持续状态:

a) 初始状态,

b) 在 ALOAD 0 之后,

c)在 ILOAD 1 之后,

d) 在PUTFIELD 之后

现在让我们考虑一个稍为复杂一点的方法

这个新 setter 方法的字节代码如下:

第一条指令将初始化为 f 的局部变量 1 压入操作数栈。IFLT 指令从栈中弹出这个值,并将它与 0 进行比较。如果它小于(LT)0,则跳转到由 label 标记指定的指令,否则不做任何事情,继续执行下一条指令。接下来的三条指令与 setF 方法中相同。GOTO 指令无条件跳转到由end 标记指定的指令,也就是 RETURN 指令。label 和 end 标记之间的指令创建和抛出一个异常:NEW 指令创建一个异常对象,并将它压入操作数栈中。DUP 指令在栈中重复这个值。

INVOKESPECIAL 指令弹出这两个副本之一,并对其调用异常构造器。最后,ATHROW 指令弹出剩下的副本,并将它作为异常抛出(所以不会继续执行下一条指令)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值