JVM之java栈
简介
与程序计数器一样,Java虚拟机栈(JavaVirtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
每当启动一个新线程时,Java虚拟机都会为它分配一个Java栈。Java栈以帧为单位保存线程的运行状态。虚拟机只会直接对Java栈执行两种操作:以帧为单位的压栈和出栈。
某个线程正在执行的方法被称为该线程的当前方法。当前方法使用的栈帧称为当前帧,当前方法所属的类称为当前类,当前类的常量池称为当前常量池。在线程执行一个方法时,它会跟踪当前类和当前常量池。此外,当虚拟机遇到栈内操作指令时,它对当前帧内数据执行操作。
每当线程调用一个Java方法时,虚拟机都会在该线程的Java栈中压入一个新帧。而这个新帧自然就成了当前帧。在执行这个方法时,它使用这个帧来存储参数、局部变量、中间运算结果等数据。
Java方法可以以两种方式完成。一种通过return正常返回;一种通过抛异常返回。不管怎么返回,虚拟机都会将当前帧弹出栈并释放,这样上一个方法的帧就成了当前帧。
Java栈上的所有数据都是此线程私有的。任何线程都不能访问另一个线程的栈数据,因此我们不需要考虑多线程情况下栈数据的访问同步问题。当一个线程调用一个方法时,方法的局部变量保存在调用线程Java栈的帧中。只有一个线程能总是访问那些局部变量,即调用方法的线程。
栈帧
栈帧由三部分组成:局部变量区、帧数据区、操作数栈。局部变量区和操作数栈的大小要视对应的方法而定,它们是按字长计算的。编译器在编译时就确定了这些值并放在class文件中。而帧数据区的大小依赖于具体的实现。
当虚拟机调用一个Java方法时,它从对应类的类型信息(方法区)中得到此方法的局部变量区和操作数栈的大小,并据此分配栈帧内存,然后压入Java栈中。
局部变量区:
Java栈帧的局部变量区被组织为一个以字长为单位、从0开始计数的数组。字节码指令通过从0开始的索引来使用其中的数据。如:
Class Example{
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;
}
}
runClassMethod的局部变量区:
索引 | 类型 | 参数 |
0 | int | Int i |
1 | long | Long l |
2 |
|
|
3 | Float | Float f |
4 | double | Double d |
6 | reference | Object o |
7 | int | Byte b |
runInstanceMethod的局部变量区:
索引 | 类型 | 参数 |
0 | Reference | Hidden this |
1 | int | Char c |
2 | double | d |
3 |
|
|
4 | int | Short s |
5 | int | Boolean b |
注意上面图标的几个事项:
1. 类型为int、float、reference、returnAddress的值在数组中只占据一项。
2. 类型为byte、short、char的值在存入数组前都将被转换为int值,他们在栈帧中被当做int来处理,只有当它被存回堆或方法区时,才会转换回原来的类型。
3. Long和double的值在数组中占据连续的两项。
4. runInstanceMethod方法的第一项是一个隐含的reference,代表this,对于任何一个实例方法都是隐含加入的,而由于runClassMethod方法是类方法,所以数组中没有此项。
5. 注意输入参数Object o被标记为reference,在Java中,所有的对象都是按引用传递,并且都存储在堆中,永远都不会在局部变量区或操作数栈中发现对象的拷贝,只会有对象的引用。
操作数栈
和局部变量区一样,操作数栈也是被组织成一个以字长为单位的数组,但是和前者不同的是,他不是通过索引来访问的,而是通过标准的栈操作——压栈和出栈来访问的。
实例:
Iload_0 //把int类型的索引为0的局部变量区数据压入操作数栈
Iload_1 //把int类型的索引为1的局部变量区数据压入操作数栈
Iadd //操作数栈数据相加
Istore_2 //把操作数栈的数据出栈,存储到局部变量区索引为2的位置
下边是具体快照:
| 开始之前 | Iload_0 | Iload_1 | iadd | Istore_2 | |||||
局部变量区 | 0 | 100 | 0 | 100 | 0 | 100 | 0 | 100 | 0 | 100 |
1 | 98 | 1 | 98 | 1 | 98 | 1 | 98 | 1 | 98 | |
2 |
| 2 |
| 2 |
| 2 |
| 2 | 198 | |
帧数据区 | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | |
操作数栈 |
|
|
| 100 |
| 100 |
| 198 |
|
|
|
|
|
|
| 98 |
|
|
|
|