JVM规范的栈帧Frames

栈帧Frames

  1. 代码中方法的调用在JVM中转换成的是字节码执行,字节码指令执行的数据结构就是栈帧(stack frame),也就是在虚拟机栈中的栈元素。

  2. 栈帧是用来存储数据和部分结果的数据结构,同时也用来处理动态链接、方法返回值和异常分派。

  3. 栈帧随着方法调用而创建,随着方法调用完成(不论是正常完成或者异常完成)而销毁,

  4. 栈帧的存储空间由创建它的线程分配在Java虚拟机栈之中,每一个栈帧都有自己的本地变量表、操作数栈和指向当前方法所属的类的运行时常量池的引用。

  5. 本地变量表和操作数栈的大小在编译期确定,并由与栈帧相关的方法代码一起被提供。因此,栈帧数据结构的大小仅仅取决于Java虚拟机的实现。实现者可以在调用方法的时候给它们分配内存。

  6. 在某条线程执行的过程中的某个时间点,只有一个栈帧(用于执行方法的栈帧)处于活动状态。这个栈帧称为current frame,这个栈帧对应的方法称为current method,定义这个方法的类称作current class。对局部变量表和操作数栈的各种操作,通常都是指对当前栈帧的局部变量表和操作数栈所进行的操作。

  7. 如果当前方法调用了其他方法,或者当前方法执行结束,那这个方法的栈帧就不再是当前栈帧了。调用新方法时,新的栈帧也会随之而创建,并且会随着程序控制权移交到新方法而成为新的当前栈帧。方法返回之际,当前栈帧会传回此方法给前一个栈帧,然后虚拟机会丢弃当前栈帧,使得前一个栈帧重新成为当前栈帧。

  8. 需要特别注意的是,栈帧是线程本地私有的数据,不可能在一个栈帧之中引用另外一个线程的栈帧。

先贴一张示意图,对比图来看内容.不然真的太枯燥了.
在这里插入图片描述

    栈帧的数据结构主要分为四个部分:局部变量表、操作数栈、动态链接以及方法返回地址(包括正常调用和异常调用的完成结果)。下面就一一介绍下这四种数据结构。

局部变量表-Local Variables

基本概念
  1. 每个栈帧内部都包含一组称为局部变量表的变量列表。帧的局部变量数组的长度在编译时确定,并以类或接口的二进制表示形式提供,同时被与帧关联的方法的代码提供.
  2. 一个局部变量可以保存一个类型为boolean、byte、char、short、int、float、reference或returnAddress的数据。
  3. 两个局部变量可以保存一个类型为long或double的数据, 通过较小位置的索引来获取。
  4. 局部变量使用索引来进行定位访问。首个局部变量的索引值为0。局部变量的索引值是个整数,它大于等于0,且小于局部变量表的长度。
  5. Java虚拟机使用局部变量表来完成方法调用时的参数传递。当调用类方法时,它的参数将会依次传递到局部变量表中从0开始的连续位置上。
  6. 当调用实例方法时,第0个局部变量一定用来存储该实例方法所在对象的引用(即Java语言中的this关键字)。后续其他参数将会传递至局部变量表中从1开始的连续位置上。
简单说明

局部变量表是有索引的,就像数组一样。从0开始,到表的最大索引,也就是Slot的数量-1。

要注意的是,方法参数的个数 + 局部变量的个数 ≠ Slot的数量。因为Slot的空间是可以复用的,当pc计数器的值已经超出了某个变量的作用域时,下一个变量不必使用新的Slot空间,可以去覆盖前面那个空间。

操作数栈-Operand Stacks

基本概念
  1. 每个栈帧内部都包含一个称为操作数栈的后进后出栈。栈帧中操作数栈的最大深度由编译期决定,同时被与帧关联的方法的代码提供。
  2. 栈帧刚创建的时候操作数栈是空的。
  3. Java虚拟机提供一些字节码指令来从局部变量表或者对象实例的字段中复制常量或变量值到操作数栈中,
  4. 也提供了一些指令用于从操作数栈取走数据、操作数据以及把操作结果重新入栈。
  5. 在调用方法时,操作数栈也用来准备调用方法的参数以及接收方法返回结果。例如iadd字节码指令的作用是将两个int类型的数值相加,它要求在执行之前操作数栈的栈顶已经存在两个由前面的其他指令所放入的int类型数值。在执行iadd指令时,两个int类型数值出栈,相加求和之后求和结果重新入栈。
  6. 操作数栈的每个位置上可以保存一个Java虚拟机中定义的数据类型的值,包括long和double类型。
  7. 必须以适合操作数堆栈类型的方式操作操作操作数堆栈中的值, 给操作数栈中push两个int值并随后将其视为long或push两个浮点值并随后使用iadd指令添加它们是不允许的。
  8. 少量Java虚拟机指令(dup指令(§dup)和swap(§swap))作为原始值操作运行时数据区域,而不考虑它们的特定类型;这些指令的定义方式不能用于修改或分解单个值。这些操作数堆栈操作的限制是通过class文件验证强制执行的
  9. 在任意时刻,操作数栈都会有一个确定的栈深度,一个long或者double类型的数据会占用两个单位的栈深度,其他数据类型则会占用一个单位的栈深度。

动态链接-Dynamic Linking

基本概念
  1. 每一个栈帧都包含对current method类型的运行时常量池的引用,以支持方法代码的动态链接(Dynamic Linking)。
  2. 动态链接就是将符号引用所表示的方法,转换成方法的直接引用。
  3. 加载阶段或第一次使用时转化为直接引用的(将变量的访问转化为访问这些变量的存储结构所在的运行时内存位置)就叫做静态解析。
  4. JVM的动态链接还支持运行期转化为直接引用。也可以叫做Late Binding,晚期绑定。
形象说明

   &nbsp一个方法调用另一个方法,或者一个类使用另一个类的成员变量时,总得知道被调用者的名字吧?(你可以不认识它本身,但调用它就需要知道他的名字)。符号引用就相当于名字,这些被调用者的名字就存放在Java字节码文件里。

名字是知道了,但是Java真正运行起来的时候,真的能靠这个名字(符号引用)就能找到相应的类和方法吗?

需要解析成相应的直接引用,利用直接引用来准确地找到。

例子

举个例子,就相当于我在0X0300H这个地址存入了一个数526,为了方便编程,我把这个给这个地址起了个别名叫A, 以后我编程的时候(运行之前)可以用别名A来暗示访问这个空间的数据,但其实程序运行起来后,实质上还是去寻找0X0300H这片空间来获取526这个数据的。

这样的符号引用和直接引用在运行时进行解析和链接的过程,叫动态链接。

动态链接的前提

每一个栈帧内部都要包含一个指向运行时常量池的引用,来支持动态链接的实现。

方法调用正常完成-Normal Method Invocation Completion

基本概念

当一个方法开始执行后, 只有两种方式可以退出这个方法。第一种方式是执行引擎遇到任意一个方法返回的字节码指令, 这时候可能会有返回值传递给上层的方法调用者( 调用当
前方法的方法称为调用者) , 是否有返回值和返回值的类型将根据遇到何种方法返回指令来决定, 这种退出方法的方式称为方法调用正常完成

Java虚拟机根据不同数据类型有不同的底层return指令。当被调用方法执行某条return指令时,会选择相应的return指令来让值返回(如果该方法有返回值的话)。

重点

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

方法调用异常完成-Abrupt Method Invocation Completion

另外一种退出方式是, 在方法执行过程中遇到了异常, 并且这个异常没有在方法体内得到处理, 无论是Java 虚拟机内部产生的异常, 还是代码中使用athrow 字节码指令产生的异
常, 只要在本方法的异常表中没有搜索到匹配的异常处理器, 就会导致方法退出, 这种退出方法的方式称为方法调用异常完成 。一个方法使用Abrupt Method Invocation Completion的方式退出, 是不会给它的上层调用者产生任何返回值的。

方法返回总结

无论采用何种退出方式, 在方法退出之后, 都需要返回到方法被调用的位置, 程序才能继续执行, 方法返回时可能需要在栈帧中保存一些信息, 用来帮助恢复它的上层方法的执行
状态。一般来说, 方法正常退出时, 调用者的PC 计数器的值可以作为返回地址, 栈帧中很可能会保存这个计数器值。而方法异常退出时, 返回地址是要通过异常处理器表来确定的,栈帧中一般不会保存这部分信息。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值