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

基于栈的运行方式

Java虚拟机的执行过程基于字节码指令,可以将其视为对操作系统的一种抽象模拟。Java虚拟机具有自己的指令集和运行环境,包括堆(Heap)、栈(Stack)、方法区(Method Area)等。因此,Java虚拟机的指令操作流程与处理器的指令操作流程有许多相似之处,主要包括取指令、解码指令、执行指令以及更新计算等步骤。

Java虚拟机的指令集架构(Instruction Set Architecture,ISA)主要有两种实现方式:基于栈和基于寄存器。基于寄存器的指令集由于指令直接在寄存器中执行,因此执行效率较高。然而,由于寄存器是由硬件直接提供的,因此其数量和性能受到硬件的限制。

相比之下,基于栈的指令集的优点在于其高度的可移植性和相对容易的实现方式。寄存器的分配和管理是基于寄存器的指令集实现的一个主要难点。然而,基于栈的指令集的缺点是其执行速度相对较慢,这主要有两个原因:

1)基于栈的指令集需要维护出栈和入栈操作,这需要更多的指令,从而间接降低了执行效率。

2)基于栈的指令集的操作是对内存的访问,相比于处理器,内存的访问速度较慢。以下代码为例,来看两种指令集架构的差异:

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

编译出来基于寄存器的指令:

add ax bx // AX寄存器的值为1,BX寄存器的值为2,将结果放入AX

编译出来基于栈的指令:

java 复制代码
1 iload_0	// 操作数栈读取局部变量的第1个slot
2 iload_1	// 操作数栈读取局部变量的第2个slot
3 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方法,以及对应的字节码指令,展示了操作数栈的使用:

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

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

java 复制代码
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指令从操作数栈弹出顶部值作为方法的返回值。

未完待续

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

相关推荐
自由鬼38 分钟前
如何处理Y2K38问题
java·运维·服务器·程序人生·安全·操作系统
_oP_i4 小时前
RabbitMQ 队列配置设置 RabbitMQ 消息监听器的并发消费者数量java
java·rabbitmq·java-rabbitmq
Monkey-旭4 小时前
Android Bitmap 完全指南:从基础到高级优化
android·java·人工智能·计算机视觉·kotlin·位图·bitmap
我爱996!4 小时前
SpringMVC——响应
java·服务器·前端
小宋10215 小时前
多线程向设备发送数据
java·spring·多线程
大佐不会说日语~6 小时前
Redis高频问题全解析
java·数据库·redis
寒水馨6 小时前
Java 17 新特性解析与代码示例
java·开发语言·jdk17·新特性·java17
启山智软6 小时前
选用Java开发商城的优势
java·开发语言
鹦鹉0076 小时前
SpringMVC的基本使用
java·spring·html·jsp
R cddddd6 小时前
Maven模块化开发与设计笔记
java·maven