欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。

什么是栈帧?

正如大家所了解的,Java虚拟机的内存区域被划分为程序计数器、虚拟机栈、本地方法栈、堆和方法区。(什么?你还不知道,赶紧去看看《Java虚拟机内存结构及编码实战》)这次要介绍的栈帧(Stack Frame),就是Java虚拟机中的虚拟机栈(Virtual Machine Stack)的基本元素,它也是用于支持Java虚拟机进行方法调用和方法执行背后的数据结构,了解了它就可以更好地理解Java虚拟机执行引擎是如何运行的。

每一个方法从调用开始至执行结束的整个过程,都对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息,在同一时刻、同一条线程中,只有位于栈顶的方法才是在运行的,只有位于栈顶的栈帧才是生效的,执行引擎所运行的所有字节码指令都只针对当前栈帧进行操作。虚拟机栈和栈帧的总体结构如下图:

接下来,再分别介绍一下栈帧中的局部变量表、操作数栈、动态连接、方法返回地址等各个部分的作用和数据结构。

欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。

局部变量表(Local Variables Table)

局部变量表是用来存储一组变量值的内存空间,用于存放方法参数和方法内部定义的局部变量。在已经编译好的Class文件中,方法的Code属性的max_locals数据项中,就确定了该方法所需分配的局部变量表的最大容量。

局部变量表的容量以变量槽(Variable Slot)为最小单位,每个变量槽存放一个32位数据类型,如boolean、byte、char、short、int、float和reference这几种类型。前6种类型同学们应该都了解,就不必多介绍了,reference类型表示对一个对象实例的引用,通过这个引用做到两件事情:根据引用直接或间接地查找到实例在Java堆中的数据存放的起始地或索引;根据引用直接或间接地查找到在方法区中的存储的类信息。对于64位数据类型,如long和double这两种类型,是以高位对齐的方式为其分配两个连续的变量槽空间。

使用局部变量表时,通过索引定位对应数据的位置,索引值的范围是从0开始至局部变量表最大的变量槽数量。如果访问的是32位数据类型的变量,索引N就代表了使用第N个变量槽,如果访问的是64位数据类型的变量,则说明会同时使用第N和N+1两个变量槽。对于两个相邻的共同存放一个64位数据的两个变量槽,虚拟机不允许采用任何方式单独访问其中的某一个,如果遇到进行这种操作的字节码,Java虚拟机就会在类加载的校验阶段中抛出异常。

欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。

当一个方法被调用时,会使用局部变量表来完成参数值到参数变量列表的传递过程。如果执行的是对象实例的成员方法(没有被static修饰的方法),那么局部变量表中第0位索引的变量槽默认就是该对象实例的引用,在方法中可以通过关键字this来访问到这个隐含的参数。其余参数则按照参数表顺序排列,参数表分配完毕后,再根据方法体内部定义的局部变量顺序和作用域分配其余的变量槽。为了尽可能节省栈帧所耗的内存空间,局部变量表中的变量槽是可以重用的,当方法体中定义的局部变量超出其作用域时,该局部变量对应的变量槽就可以交给其他变量来重用。

之前的《JVM的类加载机制详解》中介绍过,在类加载过程中,类变量有两次赋初始值的过程,一次在准备阶段,赋予系统初始值;另外一次在初始化阶段,赋予代码中定义的初始值。因此即使没有为类变量赋值也没有关系,类变量仍然具有一个确定的初始值,不会产生歧义。但是局部变量不像类变量有那样的“准备阶段”,如果一个局部变量定义了但没有赋初始值,那它是完全不能使用的。所以不要认为Java中任何情况下都存在诸如整型变量默认为0、布尔型变量默认为false等这样的默认值规则。比如:

public class OneMoreStudy {
public static void main(String[] args) {
int i;
System.out.println(i);
}
}

因为局部变量i没有初始,在编译过程就会报错:

Error:(4, 28) java: 可能尚未初始化变量i

欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。

操作数栈(Operand Stack)

操作数栈是一个后入先出(Last In First Out,LIFO)栈。和局部变量表一样,在已经编译好的Class文件中,方法的Code属性的max_stacks数据项中,就确定了该方法所需分配的操作数栈的最大深度。在方法执行的任何时候,操作数栈的深度都不会超过在max_stacks数据项中设定的最大值。操作数栈的每一个元素都可以是包括long和double在内的任意Java数据类型。32位数据类型所占的栈容量为1,64位数据类型所占的栈容量为2。

当一个方法刚刚开始执行的时候,该方法的操作数栈是空的,在该方法的执行过程中,会有各种字节码指令对操作数栈进行出栈和入栈的操作。比如,整数加法的字节码指令iadd,在该指令执行前必须保证操作数栈中最接近栈顶的两个元素已经存入了两个int型的数值,当该指令执行时,会把这两个int值出栈并相加,然后将相加的结果重新入栈。

操作数栈中元素的数据类型必须与字节码指令的序列严格匹配,在编译代码时,编译器会严格保证这一点,在类加载的校验阶段也会再次验证这一点。在上面的iadd指令中,只能用于整型数的加法,它在执行时,最接近栈顶的两个元素的数据类型必须为int型,不能出现其他数据类型使用iadd命令相加的情况。

一个方法调用另外一个方法时,可以通过操作数栈来进行方法参数的传递。虽然在Java虚拟机规范中,两个不同栈帧作为不同方法的虚拟机栈的元素,是完全相互独立的。但是在大多Java虚拟机的实现时,都会进行一些优化:两个不同方法的栈帧出现一部分重叠。让下面栈帧的部分操作数栈与上面栈帧的部分局部变量表重叠在一起,这样做不仅节约了一些内存空间,更重要的是在进行方法调用时就可以直接共用一部分数据,不需要进行额外的参数复制和传递,如下图:

欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。

动态连接(Dynamic Linking)

每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。

之前的《Class文件结构全面解析》中介绍过,Class文件的常量池中存有大量的符号引用,这些符号引用一部分会在类加载阶段或者第一次使用的时候就被转化为直接引用(实际运行时内存布局中的入口地址),这种转化被称为静态解析。另外一部分将在每一次运行期间都转化为直接引用,这部分就称为动态连接。关于这两个转化过程的具体过程,这里先卖个关子,后续的文章会详细介绍。

欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。

方法返回地址

方法返回时可能需要在栈帧中保存一些信息,用来于恢复调用者(调用当前方法的方法)的执行状态。一般来说,方法正常退出时,调用者的程序计数器的值就可以作为返回地址,栈帧中很可能会保存这个计数器值。而方法异常退出时,返回地址是要通过异常处理器表来确定的,栈帧中就一般不会保存这部分信息。

方法返回的过程实际上等同于把当前栈帧出栈,可能执行的操作有:恢复调用者的局部变量表和操作数栈,把返回值(如果有的话)压入调用者栈帧的操作数栈中,调整程序计数器的值使其指向方法调用指令后面的一条指令等等。

欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。

附加信息

在Java虚拟机规范中,允许Java虚拟机增加一些规范里没有描述的信息到栈帧之中,比如:调试、性能收集相关的信息,这部分信息完全取决于具体的虚拟机实现。一般会把动态连接、方法返回地址和其他附加信息全部归为一类,称为栈帧信息。

欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。

总结

栈帧是Java虚拟机中的虚拟机栈的基本元素,每一个方法从调用开始至执行结束的整个过程,都对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址和其他附加信息。局部变量表用于存放方法参数和方法内部定义的局部变量;各种字节码指令执行时,会对操作数栈进行出栈和入栈的操作;动态连接是指向运行时常量池中该栈帧所属方法的引用;方法返回地址用于恢复调用当前方法的方法的执行状态。

欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。

详细解析Java虚拟机的栈帧结构的更多相关文章

  1. Java虚拟机之栈帧

    写在前面的话:Java虚拟机是一门学问,是众多Java大神们的杰作,由于我个人水平有限,精力有限,不能保证所有的东西都是正确的,这里内容都是经过深思熟虑的,部分引用原著的内容,讲的已经很好了,不在累述 ...

  2. Java虚拟机运行时栈帧结构--《深入理解Java虚拟机》学习笔记及个人理解(二)

    Java虚拟机运行时栈帧结构(周志明书上P237页) 栈帧是什么? 栈帧是一种数据结构,用于虚拟机进行方法的调用和执行. 栈帧是虚拟机栈的栈元素,也就是入栈和出栈的一个单元. 2018.1.2更新(在 ...

  3. 深入理解java虚拟机(十) Java 虚拟机运行时栈帧结构

    运行时栈帧结构 栈帧(Stack Frame) 是用于虚拟机执行时方法调用和方法执行时的数据结构,它是虚拟栈数据区的组成元素.每一个方法从调用到方法返回都对应着一个栈帧入栈出栈的过程. 每一个栈帧在编 ...

  4. 第7篇-为Java方法创建栈帧

    在 第6篇-Java方法新栈帧的创建 介绍过局部变量表的创建,创建完成后的栈帧状态如下图所示. 各个寄存器的状态如下所示. // %rax寄存器中存储的是返回地址 rax: return addres ...

  5. 转:二十一、详细解析Java中抽象类和接口的区别

    转:二十一.详细解析Java中抽象类和接口的区别 http://blog.csdn.net/liujun13579/article/details/7737670 在Java语言中, abstract ...

  6. C语言函数调用及栈帧结构

    source:http://blog.csdn.net/qq_29403077/article/details/53205010 一.地址空间与物理内存 (1)地址空间与物理内存是两个完全不同的概念, ...

  7. 【转载】深入理解Java虚拟机笔记---运行时栈帧结构

    栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区的虚拟机栈(Virtual Machine Stack)的栈元素.栈帧存储了方法的局部变量表,操作 ...

  8. 图解JVM字节码执行引擎之栈帧结构

    一.执行引擎      “虚拟机”的概念是相对于“物理机”而言的,这两种“机器”都有执行代码的能力.物理机的执行引擎是直接建立在硬件处理器.物理寄存器.指令集和操作系统层面的:而“虚拟机”的执行引擎是 ...

  9. 深入解析java虚拟机-jvm运行机制

    转自oschina 一:JVM基础概念 JVM(Java虚拟机)一种用于计算设备的规范,可用不同的方式(软件或硬件)加以实现.编译虚拟机的指令集与编译微处理器的指令集非常类似.Java虚拟机包括一套字 ...

随机推荐

  1. 【CSS3】3D立方体动画

    关于CSS3的3D立方体动画 知识点: 1.每个元素有独立的坐标系 2.坐标系随当前元素的改变而发生改变 3.立方体由静态transform参数构成 4.通过给容器添加动画使立方体运动 效果图: &l ...

  2. win10 uwp xaml 绑定接口

    本文告诉大家如何在 xaml 绑定属性使用显式继承接口 早上快乐 就在你的心问了我一个问题,他使用的属性是显式继承,但是无法在xaml绑定 我写了简单的代码,一个接口和属性 public class ...

  3. P1105 数列

    题目描述 给定一个正整数 \(k(2 \le k \le 15)\) ,把所有k的方幂及所有有限个互不相等的k的方幂之和构成一个递增的序列,例如,当 \(k = 3\) 时,这个序列是: 1,3,4, ...

  4. H3C OSPF协议区域LSA发布

  5. php框架thinkphp3.2.3 配置文件bug

    bug:有前后台的项目部署阶段(DEBUG模式为false)中,修改应用配置文件后,无效,修改自定义配置文件,正常;(开发模式正常) //项目只有后台没有前台的(单独模块),直接写在模块配置中即可,不 ...

  6. Python--day38--JoinableQueue解决生产者消费者模型

    ############################# # 在消费者这一端: #每次获取一个数据 #处理一个数据 #发送一个记号:标志一个数据被处理成功 #在生产者这一端: #每一次生成一个数据 ...

  7. linux scull 的内存使用

    在介绍读写操作前, 我们最好看看如何以及为什么 scull 进行内存分配. "如何"是需要全 面理解代码, "为什么"演示了驱动编写者需要做的选择, 尽管 sc ...

  8. 【js】 vue 2.5.1 源码学习(一) 大体结构 (自写版本,非源码)

    一.整体思路 1. 首先我们需要解析data,并且data里面的属性添加为vue的属性,并且拿到属性值 . 通过 原型方法 _peoxy实现     Obsever(代理函数) ==> walk ...

  9. 2019-5-21-NuGet-符号服务器

    title author date CreateTime categories NuGet 符号服务器 lindexi 2019-05-21 11:34:40 +0800 2019-05-08 21: ...

  10. CSS3 属性学习

    fill-available表示撑满可用空间(包括高度,宽度)[此处包括padding和margin会尽可能的撑满,只对于行内块(inline-block)和块元素(block)起作用,webkit内 ...