java字节码指令的工作流程

硅谷探秘者 4126 0 0

字节代码指令

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

        操作码是一个无符号字节值——即字节代码名,由助记符号标识。例如,操作码 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 类:

package asm;
public class Bean {
	private int f;
	public int getF() {
		return this.f;
	}
	public void setF(int f) {
		this.f = f;
	}
}

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

Compiled from "Bean.java"
public class asm.Bean {
  public asm.Bean();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public int getF();
    Code:
       0: aload_0
       1: getfield      #2                  // Field f:I
       4: ireturn

  public void setF(int);
    Code:
       0: aload_0
       1: iload_1
       2: putfield      #2                  // Field f:I
       5: return
}


        getF 方法的字节代码为:

       aload_0
       getfield      #2                  // Field f:I
       ireturn

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

sdf.png

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

        a) 初始状态,

        b) 在 ALOAD 0 之后,

        c) 在 GETFIELD 之后



setF 方法的字节代码:

aload_0
       iload_1
       putfield      #2                  // Field f:I
       return

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

sdfd.png

        图 3.3 setF 方法的持续状态:

        a) 初始状态,

        b) 在 ALOAD 0 之后,

        c)在 ILOAD 1 之后,

        d) 在PUTFIELD 之后


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

public void checkAndSetF(int f) { 
    if (f >= 0) {
        this.f = f;
    } else {
        throw new IllegalArgumentException();
    }
}

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

ILOAD 1
IFLT label
ALOAD 0
ILOAD 1
PUTFIELD pkg/Bean f I GOTO end
label:
NEW java/lang/IllegalArgumentException DUP
INVOKESPECIAL java/lang/IllegalArgumentException <init> ()V ATHROW
end:
RETURN

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

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



评论区
请写下您的评论...
暂无评论...
猜你喜欢
java虚拟机(jvm) 3305 Java虚拟机由一个长度、代表着某种特定操含义(称为操,Opcode)以及跟随其后零至多个代表此操所需参数(称为操数,Operands)而构成。基本数据类型1、除了
java虚拟机(jvm) 2419 1.操数栈变量到操数栈:iload,iload_,lload,lload_,fload,fload_,dload,dload_,aload,aload_操数栈到变量:istore
其他 1456 上一篇:eclipse中使用Git管理项目(2)eclipse将项目推送到远库(github)eclipse将远克隆到本地右键空白-》import将远项目拉取到本地仓库完成下一篇:eclipse中使用git完成使用GitFlow分支
official 747 《计算机组成原理》运算器基本组成如下运算器基本组成控制器基本组成控制器基本组成完成一条步骤完成一条步骤计算机描述起来非常复杂,不太好描述,我也是截图贴在了这里(原视
工具 1551 上一篇:eclipse中使用Git管理项目(3)将远克隆到本地应用情形1.员a在master主分支上新建一个newBranch分支,然后开始在newBranch分支上开始编写代
java基础 3537 概念:Java序列化是Java对象转换为序列,而Java反序列化是序列恢复为Java对象;序列化:对象序列化最主要用处就是在传递和保存对象时候,保证对象完整性和可
工具 1341 上一篇:eclipse中使用Git管理项目(1)基本操eclipse将项目推送到远库(github)uri复制github地址填写github用户名密,点击next点击
java虚拟机(jvm) 1910 jmap是java虚拟机自带一种内存映像具,我们可以通过该具配合不同参数来查看java虚拟机内存详细信息(如序中出现所有对象数量以及占用内存大小等),以及通过虚拟机内存使用情况来定
归档
2018-11  12 2018-12  33 2019-01  28 2019-02  28 2019-03  32 2019-04  27 2019-05  33 2019-06  6 2019-07  12 2019-08  12 2019-09  21 2019-10  8 2019-11  15 2019-12  25 2020-01  9 2020-02  5 2020-03  16 2020-04  4 2020-06  1 2020-07  7 2020-08  13 2020-09  9 2020-10  5 2020-12  3 2021-01  1 2021-02  5 2021-03  7 2021-04  4 2021-05  4 2021-06  1 2021-07  7 2021-08  2 2021-09  8 2021-10  9 2021-11  16 2021-12  14 2022-01  7 2022-05  1 2022-08  3 2022-09  2 2022-10  2 2022-12  5 2023-01  3 2023-02  1 2023-03  4 2023-04  2 2023-06  3 2023-07  4 2023-08  1 2023-10  1 2024-02  1 2024-03  1 2024-04  1
标签
算法基础 linux 前端 c++ 数据结构 框架 数据库 计算机基础 储备知识 java基础 ASM 其他 深入理解java虚拟机 nginx git 消息中间件 搜索 maven redis docker dubbo vue 导入导出 软件使用 idea插件 协议 无聊的知识 jenkins springboot mqtt协议 keepalived minio mysql ensp 网络基础 xxl-job rabbitmq haproxy srs 音视频 webrtc javascript
目录
没有一个冬天不可逾越,没有一个春天不会来临。最慢的步伐不是跬步,而是徘徊,最快的脚步不是冲刺,而是坚持。