Java 字节码

发布于 2024-12-18 12:10:18 字数 9163 浏览 8 评论 0

Java 虚拟机指令由一个操作码组成,该操作码指定要执行的操作,后跟零个或多个操作数,这些操作数包含要操作的值。

操作数栈

在程序的解释执行过程中,每次在为 Java 方法分配栈桢时,jvm 虚拟机需要开辟一块额外的空间作为操作数栈,用来存储计算操作数和返回结果
每一条指令执行之前,虚拟机要求该指令的操作数已被压入操作数栈中。在执行指令时, 虚拟机会将该指令所需的操作数弹出,并将指令重新压入栈中

以加法指令 iadd 为例。假设在执行该指令前,栈顶的两个元素分别为 int 值 1 和 int 值 2,那么 iadd 指令将弹出这两个 int,并将求得的和 int 值 3 压入栈中。

由于 iadd 指令只消耗栈顶的两个元素,因此,对于离栈顶距离为 2 的元素,即图中的问号,iadd 指令并不关心它是否存在,更加不会对其进行修改。

Java 字节码中有好几条指令是直接作用在操作数栈上的。最为常见的便是 dup: 复制栈顶元素,以及 pop:舍弃栈顶元素。

dup 指令常用于复制 new 指令所生成的未经初始化的引用。例如在下面这段代码的 foo 方法中,当执行 new 指令时,Java 虚拟机将指向一块已分配的、未初始化的内存的引用压入操作数栈中。

const 系列指令

在 Java 字节码中,有一部分指令可以直接将常量加载到操作数栈上。以 int 类型为例,Java 虚拟机既可以通过 iconst 指令加载 -1 至 5 之间的 int 值,也可以通过 bipush、sipush 加载一个字节、两个字节所能代表的 int 值。

主要负责把简单的数值类型送到栈顶。该系列命令不带参数。
比如对应 int 型才该方式只能把-1,0,1,2,3,4,5(分别采用 iconst_m1,iconst_0, iconst_1, iconst_2, iconst_3, iconst_4, iconst_5)
送到栈顶。 对于 int 型,其他的数值请使用 push 系列命令(比如 bipush)。

  • iconst 将 int 型推送至栈顶,范围 [-1,5]
  • lconst 将 long 型推送至栈顶
  • fconst 将 float 型推送至栈顶
  • dconst 将 double 型推送至栈顶

代码实例:

public static void main(String[] args) {
  int a=-1;
  long b=2;
  float c=3f;
  double d=4d;
}

字节码:

public class com.example.ApplicationTests {
  public com.example.ApplicationTests();
    Code:
       0: aload_0                           // 将第一个引用类型本地变量推送至栈顶
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_m1                        // 将 int 型(-1) 推送至栈顶
       1: istore_1                         
       2: ldc2_w        #2                  // long 2l
       5: lstore_2
       6: ldc           #4                  // float 3.0f
       8: fstore        4
      10: ldc2_w        #5                  // double 4.0d
      13: dstore        5
      15: return
}

push 系列指令

该系列命令负责把 一个整形数字(长度比较小)送到到栈顶。该系列命令有一个参数,用于指定要送 到栈顶的数字。
注意该系列命令只能操作一定范围内的整形数值,超出该范围的使用将使用 ldc 命令系列。

  • bipush 将单字节的常量值(-128~127) 推送至栈顶
  • sipush 将一个短整型常量值(-32768~32767) 推送至栈顶

ldc 系列指令

Java 虚拟机还可以通过 ldc 加载常量池中的常量值。这些常量包括 int 类型、long 类型、float 类型.double 类型、String 类型以及 Class 类型的常量。对于 const 系列命令和 push 系列命令操作范围之外的数值类型常量,都放在常量池中.所有不是通过 new 创建的 String 都是放在常量池中

  • ldc 将 int, float 或 String 型常量值从常量池中推送至栈顶
  • ldc_w 将 int, float 或 String 型常量值从常量池中推送至栈顶(宽索引)
  • ldc2_w 将 long 或 double 型常量值从常量池中推送至栈顶(宽索引)

load 系列常量指令

该系列命令负责把本地变量的送到栈顶。这里的本地变量不仅可以是数值类型,还可以是引用类型。

  • iload 将指定的 int 型本地变量推送至栈顶
  • lload 将指定的 long 型本地变量推送至栈顶
  • fload 将指定的 float 型本地变量推送至栈顶
  • dload 将指定的 double 型本地变量推送至栈顶
  • aload 将指定的引用类型本地变量推送至栈顶

load 数组相关指令

  • iaload 将 int 型数组指定索引的值推送至栈顶
  • laload 将 long 型数组指定索引的值推送至栈顶
  • faload 将 float 型数组指定索引的值推送至栈顶
  • daload 将 double 型数组指定索引的值推送至栈顶
  • aaload 将引用型数组指定索引的值推送至栈顶
  • baload 将 boolean 或 byte 型数组指定索引的值推送至栈顶
  • caload 将 char 型数组指定索引的值推送至栈顶
  • saload 将 short 型数组指定索引的值推送至栈顶

存储指令 store

  • istore 将栈顶 int 型数值存入指定本地变量
  • lstore 将栈顶 long 型数值存入指定本地变量
  • fstore 将栈顶 float 型数值存入指定本地变量
  • dstore 将栈顶 double 型数值存入指定本地变量
  • astore 将栈顶引用型数值存入指定本地变量
  • iastore 将栈顶 int 型数值存入指定数组的指定索引位置
  • lastore 将栈顶 long 型数值存入指定数组的指定索引位置
  • fastore 将栈顶 float 型数值存入指定数组的指定索引位置
  • dastore 将栈顶 double 型数值存入指定数组的指定索引位置
  • aastore 将栈顶引用型数值存入指定数组的指定索引位置
  • bastore 将栈顶 boolean 或 byte 型数值存入指定数组的指定索引位置
  • castore 将栈顶 char 型数值存入指定数组的指定索引位置
  • sastore 将栈顶 short 型数值存入指定数组的指定索引位置

数组访问指令

  • void 指令 return
  • int booler,byte,char,short 指令 ireturn
  • long 指令 lreturn
  • float 指令 freturn
  • double 指令 dreturn
  • rederence 指令 areturn

pop 系列

简单对栈顶进行操作

  • pop 将栈顶数值弹出 (数值不能是 long 或 double 类型的)
  • pop2 将栈顶的一个(long 或 double 类型的) 或两个数值弹出
  • dup 复制栈顶数值(数值不能是 long 或 double 类型的) 并将复制值压入栈顶
  • dup_x1 复制栈顶数值(数值不能是 long 或 double 类型的) 并将两个复制值压入栈顶
  • dup_x2 复制栈顶数值(数值不能是 long 或 double 类型的) 并将三个(或两个)复制值压入栈顶
  • dup2 复制栈顶一个(long 或 double 类型的) 或两个(其它)数值并将复制值压入栈顶
  • dup2_x1 复制栈顶数值(long 或 double 类型的) 并将两个复制值压入栈顶
  • dup2_x2 复制栈顶数值(long 或 double 类型的) 并将三个(或两个)复制值压入栈顶

栈顶元素操作系列

  • swap 将栈最顶端的两个数值互换(数值不能是 long 或 double 类型的)
  • iadd 将栈顶两 int 型数值相加并将结果压入栈顶
  • ladd 将栈顶两 long 型数值相加并将结果压入栈顶
  • fadd 将栈顶两 float 型数值相加并将结果压入栈顶
  • dadd 将栈顶两 double 型数值相加并将结果压入栈顶
  • isub 将栈顶两 int 型数值相减并将结果压入栈顶
  • lsub 将栈顶两 long 型数值相减并将结果压入栈顶
  • fsub 将栈顶两 float 型数值相减并将结果压入栈顶
  • dsub 将栈顶两 double 型数值相减并将结果压入栈顶
  • imul 将栈顶两 int 型数值相乘并将结果压入栈顶
  • lmul 将栈顶两 long 型数值相乘并将结果压入栈顶
  • fmul 将栈顶两 float 型数值相乘并将结果压入栈顶
  • dmul 将栈顶两 double 型数值相乘并将结果压入栈顶
  • idiv 将栈顶两 int 型数值相除并将结果压入栈顶
  • ldiv 将栈顶两 long 型数值相除并将结果压入栈顶
  • fdiv 将栈顶两 float 型数值相除并将结果压入栈顶
  • ddiv 将栈顶两 double 型数值相除并将结果压入栈顶
  • irem 将栈顶两 int 型数值作取模运算并将结果压入栈顶
  • lrem 将栈顶两 long 型数值作取模运算并将结果压入栈顶
  • frem 将栈顶两 float 型数值作取模运算并将结果压入栈顶
  • drem 将栈顶两 double 型数值作取模运算并将结果压入栈顶
  • ineg 将栈顶 int 型数值取负并将结果压入栈顶
  • lneg 将栈顶 long 型数值取负并将结果压入栈顶
  • fneg 将栈顶 float 型数值取负并将结果压入栈顶
  • dneg 将栈顶 double 型数值取负并将结果压入栈顶
  • ishl 将 int 型数值左移位指定位数并将结果压入栈顶
  • lshl 将 long 型数值左移位指定位数并将结果压入栈顶
  • ishr 将 int 型数值右(符号)移位指定位数并将结果压入栈顶
  • lshr 将 long 型数值右(符号)移位指定位数并将结果压入栈顶
  • iushr 将 int 型数值右(无符号)移位指定位数并将结果压入栈顶
  • lushr 将 long 型数值右(无符号)移位指定位数并将结果压入栈顶
  • iand 将栈顶两 int 型数值作“按位与”并将结果压入栈顶
  • land 将栈顶两 long 型数值作“按位与”并将结果压入栈顶
  • ior 将栈顶两 int 型数值作“按位或”并将结果压入栈顶
  • lor 将栈顶两 long 型数值作“按位或”并将结果压入栈顶
  • ixor 将栈顶两 int 型数值作“按位异或”并将结果压入栈顶
  • lxor 将栈顶两 long 型数值作“按位异或”并将结果压入栈顶

运算指令

运算或算术指令用于对两个操作数栈上的值进行某种特定运算,并把结果重新存入到操作栈顶。
无论是哪种算术指令,都使用 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。

类型转换指令

类型转换指令可以将两种不同的数值类型进行相互转换。

  • int 类型到 long、float 或者 double 类型。
  • long 类型到 float、double 类型。
  • float 类型到 double 类型。
i2l、f2b、l2f、l2d、f2d。

窄化类型转换

i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l 和 d2f。

对象创建与访问指令

  • 创建类实例的指令:new。
  • 创建数组的指令:newarray、anewarray、multianewarray。
  • 访问类字段(static 字段,或者称为类变量)和实例字段(非 static 字段,或者称为实例变量)的指令:getfield、putfield、getstatic、putstatic。
  • 把一个数组元素加载到操作数栈的指令:baload、caload、saload、iaload、laload、faload、daload、aaload。
  • 将一个操作数栈的值存储到数组元素中的指令:bastore、castore、sastore、iastore、fastore、dastore、aastore。
  • 取数组长度的指令:arraylength。
  • 检查类实例类型的指令:instanceof、checkcast。

操作数栈管理指令

  • 将操作数栈的栈顶一个或两个元素出栈:pop、pop2。
  • 复制栈顶一个或两个数值并将复制值或双份的复制值重新压入栈顶:dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2。
  • 将栈最顶端的两个数值互换:swap。

控制转移指令

控制转移指令可以让 Java 虚拟机有条件或无条件地从指定的位置指令而不是控制转移指令的下一条指令继续执行程序。

  • 条件分支:ifeq、iflt、ifle、ifne、ifgt、ifge、ifnull、ifnonnull、if_icmpeq、if_icmpne、if_icmplt、if_icmpgt、if_icmple、if_icmpge、if_acmpeq 和 if_acmpne。
  • 复合条件分支:tableswitch、lookupswitch。
  • 无条件分支:goto、goto_w、jsr、jsr_w、ret。

方法调用和返回指令

-invokevirtual 指令用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派),这也是 Java 语言中最常见的方法分派方式。

  • invokeinterface 指令用于调用接口方法,它会在运行时搜索一个实现了这个接口方法的对象,找出适合的方法进行调用。
  • invokespecial 指令用于调用一些需要特殊处理的实例方法,包括实例初始化( init )方法、私有方法和父类方法。
  • invokestatic 调用静态方法(static 方法)。
  • invokedynamic 指令用于在运行时动态解析出调用点限定符所引用的方法,并执行该方法,前面 4 条调用指令的分派逻辑都固化在 Java 虚拟机内部,而 invokedynamic 指令的分派逻辑是由用户所设定的引导方法决定的。

-方法调用指令与数据类型无关,而方法返回指令是根据返回值的类型区分的,包括 ireturn(当返回值是 boolean、byte、char、short 和 int 类型时使用)、lreturn、freturn、dreturn 和 areturn,另外还有一条 return 指令供声明为 void 的方法、实例初始化方法以及类和接口的类初始化方法使用。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

人生戏

暂无简介

0 文章
0 评论
24 人气
更多

推荐作者

lixs

文章 0 评论 0

敷衍 

文章 0 评论 0

盗梦空间

文章 0 评论 0

tian

文章 0 评论 0

13375331123

文章 0 评论 0

你对谁都笑

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文