虚拟机栈
虚拟机栈是线程私有的,每创建一个线程,虚拟机就会为这个线程创建一个虚拟机栈,虚拟机栈表示Java方法执行的内存模型,每调用一个方法就会为每个方法生成一个栈帧(Stack Frame),用来存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法被调用和完成的过程,都对应一个栈帧从虚拟机栈上入栈和出栈的过程。虚拟机栈的生命周期和线程是相同的,计算机的多线程机制我们知道同一时刻肯定一个线程在执行只有一个栈帧在执行。
[stack]
------------------
| 局部变量表 |
------------------
| 操作数栈 |
------------------
| 动态链接 |
------------------
| 返回地址 |
-------------- ----
| 其他 |
------------------
-
局部变量表
一片连续的内存空间,用来存放方法参数,以及方法内定义的局部变量。数据类型(八大基本类型和对象引用(reference类型),文件编译为Class文件时,就在方法表的Code属性的max_locals数据项中确定了该方法需要分配的最大局部变量表的容量。
表空间单位为Slot
-
操作数栈
操作数栈也常被称为操作栈,JVM底层字节码指令集是基于栈类型的,所有的操作码都是对操作数栈上的数据进行操作。每个方法都有单独的操作数栈,至于大小编译的时候也写入到方法表的code属性的max_stacks数据项中。栈容量的单位为“字宽”,对于32位虚拟机来说,一个“字宽”占4个字节,64位虚拟机来说,一个“字宽”占8个字节。当一个方法刚刚执行的时候,这个方法的操作数栈是空的,在方法执行的过程中,会有各种字节码指向操作数栈中写入和提取值,也就是入栈与出栈操作。
-
动态链接
符号引用和直接引用在运行时进行解析和链接的过程,叫动态链接。
- 一个方法调用另一个方法,或者一个类使用另一个类的成员变量时,
- 需要知道其名字
- 符号引用就相当于名字,
- 这些被调用者的名字就存放在Java字节码文件里(.class 文件)。
- 名字是知道了,但是Java真正运行起来的时候,如何靠这个名字(符号引用)找到相应的类和方法
- 需要解析成相应的直接引用,利用直接引用来准确地找到。
- 一个方法调用另一个方法,或者一个类使用另一个类的成员变量时,
-
返回地址
当一个方法被执行后,有两种方式退出这个方法。第一种方式是执行引擎遇到任意一个方法返回的字节码指令,这时候可能会有返回值传递给上层的方法调用者(调用当前方法的的方法称为调用者),是否有返回值和返回值的类型将根据遇到何种方法返回指令来决定,这种退出方法方式称为正常完成出口(Normal Method Invocation Completion)。
另外一种退出方式是,在方法执行过程中遇到了异常,并且这个异常没有在方法体内得到处理,无论是Java虚拟机内部产生的异常,还是代码中使用athrow字节码指令产生的异常,只要在本方法的异常表中没有搜索到匹配的异常处理器,就会导致方法退出,这种退出方式称为异常完成出口(Abrupt Method Invocation Completion)。
无论采用何种方式退出,在方法退出之前,都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。
一般来说,方法正常退出时,调用者PC计数器的值就可以作为返回地址,栈帧中很可能会保存这个计数器值
而方法异常退出时,返回地址是要通过异常处理器来确定的,栈帧中一般不会保存这部分信息
方法退出的过程实际上等同于把当前栈帧出栈,因此退出时可能执行的操作有:恢复上层方法的局部变量表和操作数栈,把返回值(如果有的话)压入调用者栈帧的操作数栈中,调用PC计数器的值以指向方法调用指令后面的一条指令等。