【15】Java字节码

Java方法栈帧的组成:操作数栈+局部变量表

操作数栈

  • Java字节码是Java虚拟机所使用的的指令集。它与JVM基于栈的计算模型是分不开的。

  • 在解释执行过程中,每当为 Java 方法分配栈桢时,Java 虚拟机往往需要开辟一块额外的空间作为操作数栈,来存放计算的操作数以及返回结果。

    • 执行每条执行之前,JVM要求该指令的操作数已被压入操作数栈中
    • 在执行指令时,JVM会将该指令所需要的操作数弹出,并将该指令的结果重新压入栈中

以iadd为例说明该执行过程

  • 正常情况下,操作数栈的压入弹出都是一条条指令完成的
    在抛出异常时,JVM会清空操作数栈上到所有内容,而后将异常实例的引用压入操作数栈

直接作用在操作数栈上的Java字节码指令

dup 复制栈顶元素

dup指令常用于复制new指令生成的未经初始化的引用

  • 执行new指令时,JVM将指向一块已分配的但未初始化的内存引用压入操作数栈

  • invokespecial指令将要以这个引用为调用者,调用其构造器
    该指令将消耗操作数栈上的元素,作为它的调用者和参数

  • 这之前利用dup指令复制一份new指令的结果,并用来调用构造器

    public void dup() {
    Object o = new Object();
    }

    // 对应的字节码
    public void dup();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
    stack=2, locals=2, args_size=1
    0: new // class java/lang/Object
    3: dup
    4: invokespecial // Method java/lang/Object."<init>":()V
    7: astore_1
    8: return

pop 舍弃栈顶元素

pop指令常用于舍弃调用指令的返回结果

复制代码
  public static boolean bar() {
    return false;
  }

  public void foo() {
    bar();
  }
  // foo 方法对应的字节码如下:
  public void foo();
    0  invokestatic FooTest.bar() : boolean [24]
    3  pop
    4  return
invokestatic指令依然会将返回值压入pop方法的操作数栈
因此JVM需要执行额外的pop指令,将返回值舍弃
swap 交换栈顶两个元素的值

局部变量区

  • 字节码程序将计算的结果缓存在局部变量表

  • 局部变量表类似于一个数组,依次存放

    • this指针(针对实例方法)

    • 所传入的参数

    • 字节码中的局部变量

      public void locals(long l, float f) {
      {
      int i = 0;
      }
      {
      String s = "Hello Word";
      }
      }

      // 对应的字节码
      public void locals(long, float);
      descriptor: (JF)V
      flags: ACC_PUBLIC
      Code:
      stack=1, locals=5, args_size=3 //Java编译器在编译时就已经能确定操作数栈、局部变量表的大小以及参数个数(非static方法多一个this)
      0: iconst_0
      1: istore 4
      3: ldc // String Hello Word
      5: astore 4
      7: return

      1. locals是一个实例方法,局部变量表的第0个单元存放this指针
      2. 第一个参数为long类型,占用局部变量表的第1、2个单元
      3. 第二个参数为float类型,占用局部变量表的第3个单元
      4. 方法体内的两个代码块中,分别定义了局部变量i和s,两者的生命周期没有重合,Java编译器将它们编排至同一单元,即局部变量表的第4个单元(istore 4和astore 4)
  • 存储在局部变量区的值,通常需要加载至操作数栈中,方能进行计算,得到计算结果后再存储至局部变量数组中。这些加载、存储指令是区分类型的。例如,int 类型的加载指令为 iload,存储指令为 istore。

综合示例

复制代码
public static int bar(int i) {
  return ((i + 1) - 2) * 3 / 4;
}
// 对应的字节码如下:
Code:
  stack=2, locals=1, args_size=1 //Java编译器在编译时就已经能确定操作数栈、局部变量表的大小为1以及参数个数1个(static方法无this)
     0: iload_0
     1: iconst_1
     2: iadd
     3: iconst_2
     4: isub
     5: iconst_3
     6: imul
     7: iconst_4
     8: idiv
     9: ireturn

可以看到上图中最多使用了两个操作数栈

相关推荐
basketball6162 分钟前
Python torchvision.transforms 下常用图像处理方法
开发语言·图像处理·python
JAVA百练成神4 分钟前
深度理解spring——BeanFactory的实现
java·后端·spring
兔子蟹子6 分钟前
Java集合框架解析
java·windows·python
宁酱醇10 分钟前
各种各样的bug合集
开发语言·笔记·python·gitlab·bug
DKPT12 分钟前
正则表达式
java·数据库·笔记·学习·正则表达式
南博萬12 分钟前
java将pdf转换成word
java·pdf·word
有什么东东16 分钟前
山东大学软件学院创新项目实训开发日志(20)之中医知识问答自动生成对话标题bug修改
java·vue·bug·springboot
啊吧怪不啊吧17 分钟前
Linux常见指令介绍下(入门级)
linux·开发语言·centos
谷晓光18 分钟前
Python 中 `r` 前缀:字符串处理的“防转义利器”
开发语言·python
Tiger Z24 分钟前
R 语言科研绘图第 41 期 --- 桑基图-基础
开发语言·r语言·贴图