写点什么

a+b=c,处理器一步搞定,Java 虚拟机为啥要四步?

作者:milanyangbo
  • 2025-08-01
    北京
  • 本文字数:2110 字

    阅读完需:约 7 分钟

基于栈的运行方式 Java 虚拟机的执行过程基于字节码指令,可以将其视为对操作系统的一种抽象模拟。Java 虚拟机具有自己的指令集和运行环境,包括堆(Heap)、栈(Stack)、方法区(Method Area)等。因此,Java 虚拟机的指令操作流程与处理器的指令操作流程有许多相似之处,主要包括取指令、解码指令、执行指令以及更新计算等步骤。Java 虚拟机的指令集架构(Instruction Set Architecture,ISA)主要有两种实现方式:基于栈和基于寄存器。基于寄存器的指令集由于指令直接在寄存器中执行,因此执行效率较高。然而,由于寄存器是由硬件直接提供的,因此其数量和性能受到硬件的限制。相比之下,基于栈的指令集的优点在于其高度的可移植性和相对容易的实现方式。寄存器的分配和管理是基于寄存器的指令集实现的一个主要难点。然而,基于栈的指令集的缺点是其执行速度相对较慢,这主要有两个原因:1)基于栈的指令集需要维护出栈和入栈操作,这需要更多的指令,从而间接降低了执行效率。2)基于栈的指令集的操作是对内存的访问,相比于处理器,内存的访问速度较慢。以下代码为例,来看两种指令集架构的差异:


int a = 1int b = 2int c = a + b
复制代码


编译出来基于寄存器的指令:add ax bx // AX 寄存器的值为 1,BX 寄存器的值为 2,将结果放入 AX 编译出来基于栈的指令:


1 iload_0  // 操作数栈读取局部变量的第1个slot2 iload_1  // 操作数栈读取局部变量的第2个slot3 iadd   // 将栈顶的两个slot相加4 istore_2 // 保存到局部变量中第3个slot
复制代码


栈架构的指令运算时是与操作数栈交互,即使是数据传递这样的简单操作。这样的好处就是可以忽视具体的物理架构。默认操作数存放在操作数栈上,运算后结果存放在栈顶,指令无需显式指定操作数的来源和去向。因此栈架构指令集的代码紧凑,一般都是一个或者两个字节,但所需的指令数量会比寄存器架构多。



栈的运行时


在 Java 虚拟机中,栈帧(Stack Frame)是一个关键的数据结构,用于支持方法的执行和方法之间的调用。每个栈帧由多个重要部分构成,包括局部变量表(Local Variable Table)、操作数栈(Operand Stack)、动态链接信息(Dynamic Linking)和方法返回地址(Return Address)。局部变量表用于存储方法的参数以及方法内定义的局部变量。操作数栈则用于保存临时数据,特别是在执行算术运算或表达式时的中间计算结果。动态链接信息用于在方法调用过程中进行动态链接,这一机制是 Java 实现多态性(Polymorphism)的重要保障。方法返回地址则记录了方法执行完毕后,程序需要返回到的代码位置。当方法被调用时,Java 虚拟机会为该方法分配一个新的栈帧,并将其推入当前线程的栈顶。在方法执行过程中,虚拟机会根据字节码指令对操作数栈和局部变量表进行一系列操作,包括数据加载、存储、算术运算以及类型转换等。当方法执行完成后,无论是正常退出还是由于未捕获的异常终止,Java 虚拟机会将当前栈帧弹出,并将控制权转交给上一个栈帧,具体来说,是转交给方法返回地址所指定的位置。这个过程会持续进行,直到所有的栈帧都被弹出,标志着 Java 程序的执行结束。



操作数栈(Operand Stack),也被称为表达式栈(Expression Stack),是 Java 虚拟机执行计算的核心工作区域。它的深度是在编译期间通过代码分析计算出来的,并记录在方法的 Code 属性中。操作数栈主要负责存储指令执行过程中的中间结果。几乎所有的字节码指令都会与操作数栈进行交互。例如,iadd 指令会从操作数栈顶弹出两个整数,相加后将结果压回操作数栈。这些中间结果可以是各种 Java 数据类型,包括基本类型(如 int, float, long, double)和对象引用(reference)。此外,操作数栈还承担着在方法调用和返回过程中参数和返回值的传递任务。当方法被调用时,调用者方法计算出传递给被调用方法的参数值,并将这些参数值依次压入调用者自身的操作数栈。方法调用指令(如 invokevirtual, invokestatic)会消耗这些参数值,并将它们传递给被调用方法。在被调用方法的新栈帧中,这些参数值通常会从调用者的操作数栈转移到被调用方法栈帧的局部变量表中;当方法执行完毕并返回时,被调用方法将其计算得到的返回值(如果有)压入其自身的操作数栈顶。返回指令(如 ireturn, areturn)会将被调用方法栈帧的这个返回值弹出,并压入调用者方法的操作数栈顶,供调用者后续使用。以下是一个简单的 Java 方法,以及对应的字节码指令,展示了操作数栈的使用:


public int add(int a, int b) {    return a + b;}
复制代码


对应的字节码指令(使用 javap -c 命令查看):


public int add(int, int);   Code:      0: iload_1       // 将局部变量表索引1处的值(即参数a)压入操作数栈      1: iload_2       // 将局部变量表索引2处的值(即参数b)压入操作数栈      2: iadd          // 从操作数栈弹出两个int值,相加后将结果压入操作数栈      3: ireturn       // 从操作数栈弹出顶部int值,作为方法的返回值
复制代码


在上述字节码指令中,iload_1 和 iload_2 指令将局部变量表中的值压入操作数栈,iadd 指令从操作数栈弹出两个值进行相加操作,并将结果压回操作数栈,最后 ireturn 指令从操作数栈弹出顶部值作为方法的返回值。



未完待续


很高兴与你相遇!如果你喜欢本文内容,记得关注哦!!!

发布于: 刚刚阅读数: 2
用户头像

milanyangbo

关注

让世界知道我的存在 2018-03-05 加入

技术/人文, 互联网

评论

发布
暂无评论
a+b=c,处理器一步搞定,Java虚拟机为啥要四步?_Java虚拟机_milanyangbo_InfoQ写作社区