JVM - 栈帧结构及方法调用

方法是JVM执行的基本单位,每个方法执行都对应一个独立的执行环境,栈帧就是这个执行环境的物理体现。栈帧是一个复合数据结构,封装了方法执行所需的所有运行时所需的数据:包括局部变量表、操作数栈、动态链接、方法返回地址和一些额外的附加信息。

局部变量表

用于存储方法参数和方法内定义的局部变量,具有以下特点。

1、是一组变量值的存储空间(可以理解为数组),以变量槽(slot)为基本存储单位,按照方法参数->方法体内声明的变量->代码块内声明的变量的顺序进行分配,每个变量槽(slot)可存储一个32位数据,对于long、double两种64位的数据类型,会占据两个连续的slot;
2、对于实例方法,第一个slot(即数组下标为0)固定存储this引用;对于静态方法,没有this引用,第一个slot存储的是第一个方法参数;
3、局部变量表的大小在编译期就已经确定,存储在code属性的max_locals中,由于JVM会分析变量的作用域范围,当一个变量的作用域结束后,其占用的slot会被后续变量复用,这就导致max_locals中存储的不是所有局部变量数量的总和,而是同时存货的变量的最大数量,这样可以节省栈帧空间;
4、需要注意的是,局部变量表不存在类似类变量或者实例变量那样的"准备阶段",必须在使用前显示的进行初始化。对于native方法,局部变量表由本地方法自行管理。

各种数据类型占用的变量槽大小如下列表所示:

数据类型类别 具体类型 Slot占用 存储方式
基本数据类型 boolean, byte, char, short 1 Slot 扩展为32位存储
int, float 1 Slot 直接存储
long, double 2 Slots 分高低位存储
对象引用 类实例/数组/String等 1 Slot 存储指向堆的指针(可能压缩)
特殊 returnAddress(jsr指令用,已废弃,使用异常表替代) 1 Slot 存储字节码指令地址

从下图的demo程序可以看出上述2和3两个特点;静态方法由于没有this引用,比实例方法少一个变量槽;由于变量c在单独的代码块内,变量d会复用变量c的变量槽,导致最大变量槽数比实际定义的变量数少1。
java静态方法局部变量槽
Java普通方法局部变量槽

操作数栈

用于存储方法执行过程中的操作数和计算结果,具有以下特点:

1、是一个后入先出(LIFO)栈;和局部变量表一样,操作数栈最大大小在编译期就已经确定,存储在Code属性的max_stacks中;同时操作数栈的每一个元素都可以存储任意的java数据类型,32位数据类型占用的栈容量为1,64位数据类型占用的栈容量为2;
2、通过将字节码指令通过压栈(push)和出栈(pop)操作来进行计算;
3、在方法调用时,调用者通过操作数栈将参数传递给被调用者;

4、在概念模型中,不同栈帧的操作数栈之间数据独立,但是绝大多数虚拟机实现中会进行优化处理,会令两个栈帧出现一部分重叠;栈帧重叠是JVM的一个优化机制,将调用者栈帧的操作数栈栈顶部分和被调用者栈帧的局部变量表底部可以共享一块内存区域,可以减少内存占用、减少数据复制和提高缓存局部性;需要调用者和被调用者的参数布局一致,参数类型和数量在编译期可知,栈帧地址按机器字长对齐;

如下图所示,iload命令将变量压入栈,iadd命令将栈顶两个数弹出并相加,istore命令将计算结果存入变量槽3;下方在调用方法之前,也会先将对应参数压入操作数栈;相关命令意思可通过工具点击"显示JVM规范",查询详细介绍;
java操作数栈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值