一. 虚拟机栈
特点:
- 每创建一个线程,就会创建栈
- 线程私有
- 每个方法执行的时候,Java虚拟机都会同步创建一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法出口等信息
- 虚拟机只会直接对虚拟机栈执行两种操作:以栈帧为单位的压栈和出栈
- 栈顶位置的栈帧为当前栈帧,所属的方法为正在执行的方法 也叫 当前方法
- Java方法可以以两种方式完成。一种通过return返回的,称为正常返回;一种是通过抛出异常而异常终止的。不管以哪种方式返回,虚拟机都会将当前帧弹出Java栈然后释放掉,这样上一个方法的帧就成为当前帧了
二. 局部变量表
局部变量表是用来存放方法参数和方法内部定义的局部变量
- 局部变量表的数据结构是 数字数组,数组中的元素以局部变量槽(Slot) 来表示
- 其中32位长度的数据类型占用一个变量槽,64位长度的数据类型占用两个变量槽
- 局部变量表的大小在编译器确定,运行期不会改变(方法的 Code 属性的 max_locals 数据项中确定了该方法所需要分配的局部变量表的最大容量)
- 局部变量表可以存放基本数据类型、对象引用(这里的对象引用是指一个对象的起始地址的引用指针,或者是代表一个对象的句柄),以及returnAddress类型(指向的是一条字节码指令的地址)
方法执行完毕,局部变量表就会销毁,所以局部变量表的变量只有在当前方法有效
例:
public void sum(){
int a = 10;
int b = 15;
int c = 20;
int d = 25;
}
Start Length Slot Name Signature
0 14 0 this Lcom/haiyang/Test;
3 11 1 a I
6 8 2 b I
9 5 3 c I
13 1 4 d I
第一列 start 代表了 这个变量在字节码的行号
第二列 length代表了 这个变量的在字节码中作用域的长度
第三列 slot 代表了 这个变量 在局部变量表中的索引 (64位会占两个)
第四列 name 代表了 变量名
第五列 Signature 代表了变量的 描述符
其中值得注意的几点是:
- 如果不是静态方法,局部变量表为0的slot为 this
- 32位的类型只占用一个slot(包括returnAddress类型),64位的类型占用两个slot(long和double)
- JVM会为局部变量表中的每一个Slot都分配一个访问索引,通过这个索引即可成功访问到局部变量表中指定的局部变量值
- 分配顺序:this->方法的参数->方法内部定义的局部变量
三. 操作数栈
操作数栈其实就是类似于操作系统中用来存储接下来要被处理的数值的寄存器,不过JVM是用操作数栈来实现这类寄存器的功能
比如:
1. sum方法:
public void sum(){
int a = 10;
int b = 15;
int c = a + b;
}
Javap反编译成字节码指令后:
0: bipush 10 // 将 10 压入操作数栈
2: istore_1 // 从操作数堆栈中弹出,并存入局部变量表中1号索引的位置
3: bipush 15 // 将 15 压入操作数栈
5: istore_2 // 从操作数堆栈中弹出,并存入局部变量表中2号索引的位置
6: iload_1 // 把局部变量表中1号索引位置的值压入操作数堆栈
7: iload_2 // 把局部变量表中2号索引位置的值压入操作数堆栈
8: iadd // 弹出两个值相加,结果压入操作数栈
9: istore_3 // 从操作数堆栈中弹出,并存入局部变量表中3号索引的位置
10: return // 方法结束 没有返回值
2. getI 方法:
public int getI () {
int i = 10;
return i;
}
javap:
0: bipush 10 // 将 10 压入操作数栈
2: istore_1 // 从操作数堆栈中弹出,并存入局部变量表中1号索引的位置
3: iload_1 // 把局部变量表中1号索引位置的值压入操作数堆栈
4: ireturn // 从当前栈帧的操作数栈中弹出,并将其压入调用者的操作数堆栈
3. 特点:
- 后进先出,由数组实现的栈,只能通过入栈和出栈访问里面的元素
- 栈的最大深度在编译器就定义好了 (为方法的Code属性的max_stacks数据项)
- 操作数栈的每个元素可以是任意Java数据类型,32位的数据类型占一个栈容量,64位的数据类型占2个栈容量
- 如果方法有返回值的话,就将返回值压入调用者的操作数栈中
四. 动态链接
动态链接用来指向运行时常量池里面的方法引用
每一个栈帧都包含一个类常量池,用来存储指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接
在class文件格式的常量池中存有大量符号引用(1.类的全限定名,2.字段名和属性,3.方法名和属性),字节码的方法调用指令就是以常量池中指向方法的符号引用为参数。这些符号引用一部分会在连接的解析阶段转为直接引用,这种转化称为静态解析。还有一部分引用会在运行期间转化为直接引用,这部分称为动态连接
五. 方法返回地址
方法返回地址用来存放程序计数器存放的代码当前执行的位置
方法结束方式:
- 正常退出
- 异常退出 (通过异常完成出口退出的不会给他的上层调用者生产任何的返回值)
无论通过哪种方式退出,在方法退出后到该方法被调用的位置,方法正常退出时,调用者的程序计数器的值作为返回地址,即调用该方法的指令的下一条指令地址,而通过异常退出的,返回地址是要通过异常表来确定,栈帧中一般不会保存这部分信息
字节码指令中,返回指令包含ireturn(当返回值是boolean、byte、char、short、int类型时使用)lreturn、freturn、dreturn、areturn(return指令供声明void的方法、实例初始化方法、类和接口的初始化方法使用)
六. 附加信息
栈帧中还允许携带与Java虚拟机实现相关的一些附加信息。例如,对程序调试提供支持的信息