2.6 栈帧
栈帧用来存储变量值、中间结果,也用来进行动态链接、返回访法值和分发异常。
栈帧在一个方法调用时创建,在方法调用完成后销毁,不管方法是正常结束,还是非正常结束(方法抛出异常)。栈帧从创建此栈帧的线程对应的虚拟机栈中分配存储空间。每个栈帧有自己的本地变量表、操作数栈以及一个指向当前方法对应类的运行时常量池的引用。
本地变量表和操作数栈的大小在编译时确定,随着当前栈帧对应方法的代码一起提供。因此栈帧数据结构的大小只和虚拟机的实现相关,这些数据结构所需的内存可以在方法调用同时分配。
在一个线程中,任意时刻只有正在执行着的方法对应栈帧是活跃的。这个栈帧称为当前栈帧,对应的方法称为当前方法。当前方法所在的类称为当前类。本地变量表和操作数栈对应着当前的栈帧。
当一个方法调用另一个方法或者结束时对应的栈帧不再是当前栈帧。当一个方法被调用,控制转到新方法是,一个新的栈帧被创建,并变成当前栈帧。当方法返回时,如果当前栈帧有返回值,就会将值传递给之前栈帧,然后随着之前的栈帧变成当前栈帧此栈帧就会销毁。
一个线程创建的栈帧对本线程是私有的,任何其他的线程都不能访问
2.6.1 本地变量表
每个栈帧包含一个称为本地变量表的变量数组,变量数组的长度在编译时刻就已经确定,在一个类或接口的二进制表示中提供,随着一个栈帧对应的方法调用的编码一起提供。
单个本地变量中能存储boolean, byte, char, short, int,float,reference,returnAddress等类型的值,一对本地变量能存储long和double类型对应的一个值。
本地变量是用索引来寻址的。本地变量的第一个索引值是0。只有一个在0和变量数组长度范围内的整数值才是一个正确的索引值。
一个long和double类型的值占据连续的两个变量值。这个值只能从小的索引值访问。例如,一个double值存储在索引值为n的本地变量数组中,实际上占领n和n+1的本地变量表中,但是索引值为n+1的值是不能读取出来,只能写入,如果n+1对应的变量表中被重新写入,n对应的内容就没有实际意义。
Java虚拟机不需要存储long和double值对应的n必须是偶数。直观而言,long和double对应的值在本地变量表中并不必须是64位的。实现者可以自由决定用合适的两个变量来存储这类值。
Java虚拟机在方法调用时用本地变量表传递参数,当类方法调用时,变量从0开始按顺序连续存储到对应的本地变量表中。当实例方法调用时,本地变量0的位置总是用来传递被调用方法所在类的一个引用(即Java语言中的this),所有的变量按顺序连续存储在从1开始的本地变量表中。
2.6.2 操作数栈
每个栈帧包含一个被称为操作数栈的后进先出栈(LIFO),每个栈帧的操作数栈的深度在编译时刻就已经确定,随着一个栈帧对应的方法调用的编码一起提供。
如果上下文比较清楚,下面有的地方会直接用操作数栈代表当前栈帧对应的操作数栈。
当一个栈帧创建时对应的操作数栈是空的。Java虚拟机提供指令集把常量或数值从本地变量表或字段中加载到操作数栈中。还有一些指令从操作数栈中取出操作数,对它们进行运算,并把结果放回到操作数栈中。操作数栈也被用来准备传递参数给方法和接收方法的返回结果。例如:iadd指令把两个int类型的数值相加,这个指令需要事先有指令将被操作的两个整数值放到操作数栈的最上面,这两个整数值从操作数栈中取出来,相加,再把相加的结果放到操作数栈的最上面。操作数栈中可以嵌套子运算,其结果再被包含它的运算使用。
操作数栈中的每个条目都可以装载任意一个Java虚拟机类型的值,包括long和double类型的值。
操作数栈中的值必须按照其类型进行相应的操作。例如,你不能向操作数栈中压入两个整数,后面把它们当做long类型来操作,也不能够压入两个float类型的值,后面有iadd指令来操作。一小部分虚拟机指令如dup、swap将运行时数据区作为原始值操作而不用考虑对应的数据类型,这些指令以如此的方式定义,所以它们不能用来修改、打破单独的数值。这些在操作数栈上的一些操作限制是通过class文件来验证确保的。在任意的时点,操作数栈都有一个相应的深度,long和double类型占有两个单元,其他数据类型占用一个单元。
2.6.3 动态链接
每一个栈帧都包含一个对当前方法所在类型的运行时常量池的引用,用来支持方法代码的动态链接。在类文件中,一个方法对另外的方法调用、对变量的访问都是通过符号引用。动态链接将这些方法的符号引用转换成具体的方法调用,当需要时加载需要的类类解决到当前仍未确定的符号,将对变量的访问转换成相对应的存储架构中的偏移值,用来和这些变量在运行时存储位置相关联。
这种后期绑定方法和变量的方式,使得当方法引用的其他类变化时尽少可能的改变当前类的编码。
2.6.4 方法调用正常结束
如果一个方法调用没有抛出异常:既没有虚拟机抛出异常,也没有显式的调用throw语句,那么这个方法就是正常调用结束。如果一个方法正常结束,那么它有可能给调用它的方法返回一个值。这种情况是当被调用的方法执行一个return指令,并且return指令必须要符合方法声明的类型。
当前的栈帧这时就会加载调用者的状态,包括本地变量表、操作数栈和程序计数器,此时程序计数器会适当的增加来跳过刚执行的方法调用指令,并把刚调用的方法的返回值(如果有)压入操作数栈中,在调用者的栈中继续正常的执行。
2.6.5 方法调用异常结束
如果在方法中执行Java虚拟机指令引起虚拟机抛出一个异常,并且这个异常没有被方法捕获,那么这个方法就会异常结束。另外,执行athrow指令,也会显式的抛出一个异常,如果异常没有被方法捕获处理,也将引起方法的异常结束。一个异常结束的方法从不会给它的调用者返回值。
分享到:
相关推荐
本书共分20章,第1-4章解释了java虚拟机的体系结构,包括java栈、堆、方法区、执行引擎等;第5-20章深入描述了java技术的内部细节,包括垃圾收集、java安全模型、java的连接模型和动态扩展机制、class文件、运算及...
#### 第二章 平台无关 - **平台无关的概念**:Java的设计初衷是为了实现“一次编写,到处运行”的理念,即通过Java虚拟机使得Java程序可以在任何安装了JVM的操作系统上运行。 - **Java平台**:Java平台由Java...
第二部分则深入探讨了Java虚拟机的内部机制,涵盖了类的生命周期、链接模型、垃圾回收、栈和局部变量操作、类型转换等内容。 ### 第一部分:Java的架构 - **第1章:Java体系结构简介** - 提供了一个概览,介绍Java...
- **第二章**: 描述了Java虚拟机的基本架构,包括类文件格式、内存模型等。 - **第三章**: 详细介绍Java虚拟机的执行引擎,如栈帧、本地方法栈等。 - **第四章**: 深入探讨垃圾回收机制、内存分配策略等内容。 - **...
3.4.2 第二趟:类型数据的语义检查 3.4.3 第三趟:字节码验证 3.4.4 第四趟:符号引用的验证 3.4.5 二进制兼容 3.5 Java虚拟机中内置的安全特性 3.6 安全管理器和Java API 3.7 代码签名和认证 ...
深入java虚拟机第二版 第1章 Java体系结构介绍 1.1 为什么使用Java 1.2 网络带来的挑战和机遇 1.3 体系结构 1.3.1 Java虚拟机 1.3.2 类装载器的体系结构 1.3.3 Java class文件 1.3.4 Java API 1.3.5 ...
#### 二、Java虚拟机的重要性 Java虚拟机(JVM)是运行Java应用程序的核心组件,它负责将Java字节码转换为特定平台上的机器指令,并管理内存、处理异常、提供安全机制等功能。由于JVM的存在,Java实现了跨平台的...
3.4.2 第二趟:类型数据的语义检查 3.4.3 第三趟:字节码验证 3.4.4 第四趟:符号引用的验证 3.4.5 二进制兼容 3.5 Java虚拟机中内置的安全特性 3.6 安全管理器和Java API 3.7 代码签名和认证 ...
《深入理解Java虚拟机》是Java开发者深入了解JVM(Java Virtual Machine)的必备经典书籍,其第二版提供了原代码分析,帮助读者更直观地理解JVM的工作原理。这本书的各个章节涵盖了从内存管理到垃圾回收,从类加载...
这本书的内容是帮你全面了解java虚拟机,本书第1版两年内印刷近10次,98%以上的评论全部为5星级的好评,是整个Java图书领域公认的经典著作和超级畅销书,繁体版在台湾也十分受欢迎。第2版在第1版的基础上做了很大的...
第二章深入解析了Java虚拟机。首先,研究了Class文件规范,这是Java源代码编译后的二进制格式,包含了类的元数据和指令。然后,详细阐述了Java虚拟机规范,这是Java运行环境的核心,定义了如何解释和执行字节码。...
第二章“词法分析”是编译器的第一步,它将源代码分解成一个个称为“词法单元”的基本元素,如标识符、关键字、运算符和常量。词法分析器(也称为扫描器)使用正则表达式来识别这些元素,为后续的语法分析做好准备。...