JVM_1.7_运行时内存区域_栈帧

版权声明:本文为博主原创文章,可以转载,但请注明出处。 https://blog.csdn.net/Simba_cheng/article/details/83212412

运行时内存区域这块,如果不将内存各个区域做什么的了解清楚,后面看的会很累。

之前将JVM运行时内存区域的内容,整理在了一篇文章中。

在后续深入、细致的学习中,整理的内容越来越多,一篇的话,会导致篇幅过长。

所以将《JVM运行时内存区域详解》分为以下几个章节:

JVM_1.0_运行时内存区域

JVM_1.1_运行时内存区域_堆

JVM_1.2_运行时内存区域_Java虚拟机栈

JVM_1.3_运行时内存区域_方法区

JVM_1.4_运行时内存区域_本地方法栈

JVM_1.5_运行时内存区域_程序计数器

JVM_1.6_运行时内存区域_运行时常量池

JVM_1.7_运行时内存区域_栈帧

这里将《Java虚拟机规范中文版》上传了,点击下面链接,即可下载

Java虚拟机规范SE7中文版下载

目录

栈帧

《深入理解Java虚拟机:JVM高级特性与最佳实践》

局部变量表

操作数栈

动态链接

返回地址

《Java Virtual Machine Specification Java SE 7 中文版》

局部变量表

操作数栈

动态链接

返回地址-方法正常调用完成

返回地址-方法异常调用完成


栈帧

《深入理解Java虚拟机:JVM高级特性与最佳实践》

栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中Java虚拟机栈的栈元素

(Java虚拟机栈中存储的就是一个个的栈帧)

栈帧存储了方法的局部变量表操作数栈动态链接方法返回地址等信息

每一个方法从调用开始到执行完成的过程,就对应着一个栈帧在Java虚拟机栈中,从入栈到出栈的过程。

每一个栈帧都包括了局部变量表、操作数栈、动态链接、方法返回地址和一些额外的附加信息。

在编译程序代码的时候,栈帧中需要多大的局部变量表、多深的操作数栈都已经完全确定好了因此一个栈帧需要分配多深内存,不会受程序运行期变量数据的影响,而仅仅取决于具体的虚拟机实现

一个线程中的方法调用链可能会很长,很多方法都同时处于执行状态。

对于执行引擎来讲,活动线程中,只有在栈顶的栈帧是有效的,称为当前栈帧(Current Stack Frame),这个栈帧所关联的方法称为当前方法(Current Method),执行引擎所运行的所有字节码指令都只针对当前栈帧进行操作。

局部变量表

局部变量表是一组变量值存储空间用于存放方法参数和方法内部定义的局部变量

在方法执行时,虚拟机是使用局部变量表完成参数值到参数变量列表的传递过程

如果执行的是实例方法(非static的方法),那局部变量表中第0位索引默认用于传递方法所属对象实例的引用,在方法中可以通过关键字"this"来访问到这个隐含的参数。其余参数则按照参数表顺序排序

举个栗子(from:https://www.artima.com/insidejvm/ed2/jvm8.html

class Example3a {

    public static int runClassMethod(int i, long l, float f, double d, Object o, byte b) {
        return 0;
    }

    public int runInstanceMethod(char c, double d, short s, boolean b) {
        return 0;
    }
	
}

从上面代码和图例,很清楚的看出了局部变量表的一个状态。

操作数栈

操作数栈也常被称为操作栈,它是一个后入先出栈(Last In First Out , LIFO)

操作数栈的每一个元素可以是任意的Java数据类型,包括long和double。

当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的;

在方法的执行过程中,会有各种字节码指令向操作数栈中写入和提取内容,也就是入栈出栈操作。

例如:在做算术运算的时候是通过操作数栈来进行的。

整数加法的字节码指令iadd在运行的时候要求操作数栈中最接近栈顶的两个元素已经存入了int型的数值,当执行这个指令时,会将这两个int值出栈并相加,然后将相加结果入栈。

(这个栗子是不是有点眼熟,还记得小故事中的那个插画嘛?)

Java虚拟机的解释执行引擎称为“基于栈的执行引擎”,其中所指的“栈”就是操作数栈。

动态链接

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

返回地址

当一个方法开始执行后,只有两种方式可以退出这个方法。

  • 第一种方式:

执行引擎遇到任意一个方法返回的字节码指令,这时候可能会有返回值传递给上层的方法调用者,这种退出方法的方式称为正常完成出口

  • 第二种方式:

在方法执行过程中遇到了异常,并且这个异常没有在方法体内得到处理,无论是Java虚拟机内部产生的异常,还是代码中使用athrow字节码指令产生的异常,只要在笨方法的异常表中没有搜索到匹配的异常处理器,就会导致方法退出,这种退出方法的方式称为异常完成出口

一个方法使用异常完成出口的方式退出,是不会给它的上层调用者产生任何返回值的

无论采用何种退出方式,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。

方法退出的过程实际上等同于把当前栈帧出栈

《Java Virtual Machine Specification Java SE 7 中文版》

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

栈帧随着方法调用而创建,随着方法结束而销毁无论方法是正常完成还是异常完成(抛出了在方法内未被捕获的异常)都算作方法结束

栈帧的存储空间分配在Java虚拟机栈之中

每一个栈帧都有自己的局部变量表、操作数栈和执行当前方法所属的类的运行时常量池的引用。

栈帧容量的大小仅仅取决于 Java 虚拟机的实现和方法调用时可被分配的内存。

在一条线程之中,只有目前正在执行的那个方法的栈帧是活动的。

这个栈帧就被称为是当前栈帧( Current Frame),这个栈帧对应的方法就被称为是当前方法( Current Method),定义这个方法的类就称作当前类( Current Class)

对局部变量表和操作数栈的各种操作,通常都指的是对当前栈帧的对局部变量表和操作数栈进行的操作。

局部变量表

每个栈帧内部都包含一组称为局部变量表(Local Variables)的变量列表。

一个局部变量可以保存一个类型为 boolean、byte、char、short、float、reference和 returnAddress 的数据,两个局部变量可以保存一个类型为 long 和 double 的数据。

Java 虚拟机使用局部变量表来完成方法调用时的参数传递,当一个方法被调用的时候,它的参数将会传递至从 0 开始的连续的局部变量表位置上。

特别地,当一个实例方法被调用的时候,第0个局部变量一定是用来存储被调用的实例方法所在的对象的引用(即 Java 语言中的“this”关键字),后续的其他参数将会传递至从 1 开始的连续的局部变量表位置上

操作数栈

每一个栈帧内部都包含一个称为操作数栈(Operand Stack)的后进先出(Last-In-First-Out,LIFO)栈。

操作数栈所属的栈帧在刚刚被创建的时候,操作数栈是空的

Java虚拟机提供一些字节码指令来从局部变量表或者对象实例的字段中复制常量或变量值到操作数栈中,也提供了一些指令用于从操作数栈取走数据、操作数据和把操作结果重新入栈。

在方法调用的时候,操作数栈也用来准备调用方法的参数以及接收方法返回结果。

举个例子:

iadd字节码指令的作用是将两个int类型的数值相加,它要求在执行的之前操作数栈的栈顶已经存在两个由前面其他指令放入的 int 型数值。

在 iadd 指令执行时,2 个 int 值从操作栈中出栈,相加求和,然后将求和结果重新入栈。

每一个操作数栈的成员 (Entry) 可以保存一个 Java 虚拟机中定义的任意数据类型的值,包括 long 和 double 类型

操作数栈中的数据必须被正确地操作,这里正确操作是指对操作数栈的操作必须与操作数栈栈顶的数据类型相匹配,例如不可以入栈两个int类型的数据,然后当作long类型去操作他们,或者入栈两个float类型的数据,然后使用iadd指令去对它们进行求和。

在任意时刻,操作数栈都会有一个确定的栈深度,一个long或者double类型的数据会占用两个单位的栈深度,其他数据类型则会占用一个单位深度。

动态链接

每一个栈帧内部都包含一个指向运行时常量池(RunTime Constant Pool)的引用来支持当前方法的代码实现动态链接 (Dynamic Linking)。

在Class文件里面,描述一个方法调用了其他方法,或者访问其成员变量是通过符号引用(Symbolic Reference)来表示的,动态链接的作用就是将这些符号引用所表示的方法转换为实际方法的直接引用

类加载的过程中将要解析掉尚未被解析的符号引用,并且将变量访问转化为访问这些变量的存储结构所在的运行时内存位置的正确偏移量。

返回地址-方法正常调用完成

方法正常调用完成是指在方法的执行过程中,没有任何异常被抛出——包括直接从Java虚拟机之中抛出的异常以及在执行时通过throw语句显式抛出的异常。

如果当前方法调用正常完成的话,它很可能会返回一个值给调用它的方法,方法正常完成发生在一个方法执行过程中遇到了方法返回的字节码指令的时候(例如return),使用哪种返回指令取决于方法返回值的数据类型(如果有返回值的话)。

在这种场景下,当前栈帧承担着回复调用者状态的责任,其状态包括调用者的局部变量表、操作数栈和被正确增加过来表示执行了该方法调用指令的程序计数器等。

使得调用者的代码能在被调用的方法返回并且返回值被推入调用者栈帧的操作数栈后继续正常地执行。

返回地址-方法异常调用完成

方法异常调用完成是指在方法的执行过程中,某些指令导致了Java虚拟机抛出异常,并且虚拟机抛出的异常在该方法中没有办法处理,或者在执行过程中遇到了athrow字节码指令显示的抛出异常,并且在该方法内部没有把异常捕获住。

如果在方法异常调用完成,那一定不会有方法返回值给它的调用者。

猜你喜欢

转载自blog.csdn.net/Simba_cheng/article/details/83212412
今日推荐