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