面试题:虚拟机栈的理解

虚拟机栈

由于跨平台性设计,Java的指令都是根据栈来设计的。不同平台CPU架构不同,所以不能设计为基于寄存器的。优点是跨平台,指令集小,编译器容器实现,缺点是性能下降,实现同样的功能需要更多的指令。栈解决的是程序运行的问题,即程序如何运行,如何处理数据。生命周期和线程一致,它保存方法的局部变量,部分结果,并参与方法的调用和返回。

image-20201224001546811

栈是一种快速有效的分配存储方式,访问速度仅次于程序计数器。

对于栈来说不存在垃圾回收问题

JVM直接对Java栈的操作只有2个:每个方法执行,伴随着入栈,执行结束后的出栈工作。

栈帧

栈帧的描述:

  • 每个线程都有自己的栈,栈中的数据都是以栈帧的格式存在。
  • 在这个线程中正在执行的每个方法都各自对应一个栈帧
  • 栈帧是一个内存区块,是一个数据集,维系着方法执行过程中的各种数据信息。
  • 在一条活动线程中,一个时间点上,只会有一个活动的栈帧。即当前正在执行的方法的栈帧有效,这个栈帧被称为当前栈帧。执行引擎所有的字节码指令都是针对当前栈帧进行操作的。

栈帧的原理:

  • 不同线程中所包含的栈帧是不允许存在相互引用的,即不可能存在一个栈帧之中引用另外一个线程的栈帧。
  • 如果当前方法调用其他方法,方法返回之际,当前栈帧回将执行结果返回给前一个栈帧,接着被虚拟机废弃,前一个栈帧成为当前栈帧。

image-20201224220706155

1.局部变量表

局部变量表是一组局部变量值存储空间,用于存放方法参数和方法内部定义的局部变量。在Java文件编译为Class文件时,就在方法表的Code属性的max_locals数据项中确定了该方法需要分配的最大局部变量表的容量。由于局部变量表是建立在线程的栈上,是线程私有数据,因此不存在数据安全问题。

局部变量表中的变量只在当前方法调用中有效。在方法执行时,虚拟机通过使用局部变量表完成参数值到参数变量列表的传递过程。当方法调用结束后,随着方法栈帧的销毁也随之销毁。

局部变量表最基本的存储单元是Slot,32位以内的类型只占用一个solt,64位的类型占用2个slot。局部变量表会为每一个slot都分配一个访问索引,通过这个索引即可成功访问到局部变量表中指定的变量值。如果需要访问局部变量表中一个64bit的局部变量值时,只需要使用前一个索引即可。栈帧中的局部变量表中的槽位是可以重用的。局部变量表不存在系统初始化的过程,这意味着一旦定义了局部变量则必须人为的初始化,否则无法使用。

2.操作数栈

操作数栈,主要是用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。它是JVM执行引擎的一个工作区,当一个方法刚开始执行的时候,一个新的栈帧也会随之被创建出来,这个方法的操作数栈是空的。每一个操作数栈都会拥有一个明确的栈深度用于存储数值,其所需的最大深度在编译时期就定义好了,保存在方法的Code属性中,为max_stack的值。

32bit的类型占用一个栈的单位深度,64bit的类型占用2个栈单位深度。操作数栈并非采用访问索引的方式来进行数据访问的,而是只能通过标准的入栈和出栈操作来完成一次数据访问。

如果被调用的方法带有返回值的话,其返回值将会被压入当前栈帧的操作数栈中,并更新PC寄存器中下一条需要执行的字节码指令。

栈顶缓存技术:将栈顶元素全部缓存在物理CPU的寄存器中,以此降低对内存的读写次数,提升执行引擎的执行效率。

3.动态连接

每个栈帧都包含一个指向运行时常量池中该栈帧所属性方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。在Class文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用一部分会在类加载阶段或第一次使用的时候转化为直接引用,这种转化称为静态解析。另外一部分将在每一次的运行期期间转化为直接引用,这部分称为动态连接。那么动态链接的作用就是为了将这些符号引用转化为调用方法的直接引用。

image-20201224223854950

4.方法返回地址

当一个方法被执行后,有两种方式退出这个方法。第一种方式是执行引擎遇到任意一个方法返回的字节码指令,这时候可能会有返回值传递给上层的方法调用者(调用当前方法的的方法称为调用者),是否有返回值和返回值的类型将根据遇到何种方法返回指令来决定,这种退出方法方式称为正常完成出口(Normal Method Invocation Completion)。另外一种退出方式是,在方法执行过程中遇到了异常,并且这个异常没有在方法体内得到处理,无论是Java虚拟机内部产生的异常,还是代码中使用athrow字节码指令产生的异常,只要在本方法的异常表中没有搜索到匹配的异常处理器,就会导致方法退出,这种退出方式称为异常完成出口(Abrupt Method Invocation Completion)。一个方法使用异常完成出口的方式退出,是不会给它的调用都产生任何返回值的。 无论采用何种方式退出,在方法退出之前,都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。一般来说,方法正常退出时,调用者PC计数器的值就可以作为返回地址,栈帧中很可能会保存这个计数器值。而方法异常退出时,返回地址是要通过异常处理器来确定的,栈帧中一般不会保存这部分信息。 方法退出的过程实际上等同于把当前栈帧出栈,因此退出时可能执行的操作有:恢复上层方法的局部变量表和操作数栈,把返回值(如果有的话)压入调用都栈帧的操作数栈中,调用PC计数器的值以指向方法调用指令后面的一条指令等。

5.附加信息

虚拟机规范允许具体的虚拟机实现增加一些规范里没有描述的信息到栈帧中,例如与高度相关的信息,这部分信息完全取决于具体的虚拟机实现。在实际开发中,一般会把动态连接,方法返回地址与其它附加信息全部归为一类,称为栈帧信息。

posted @ 2020-12-24 23:22  天宇轩-王  阅读(122)  评论(0编辑  收藏  举报