与程序计数器一样, Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。Java栈以帧为单位保存线程的运行状态。每个方法在执行的时候都会创建一个栈帧用于存储局部变量表、操作栈、动态链接、 方法出口等信息。虚拟机只会直接对Java栈执行两种操作: 以帧为单位的压栈或出栈。
Java方法可以以两种方式完成。一种通过return返回,称为正常返回;一种是通过抛出异常而中止的。不管以哪种方式返回,虚拟机都会将当前帧弹出Java栈然后释放掉,这样上一个方法的帧就成为当前帧了。
Java栈上的所有数据都是此线程私有的。任何线程都不能访问另一个线程的栈数据, 因此我们不需要考虑多线程情況下栈数据的访问同步问题。当一个线程调用一个方法时,方法的局部变量保存在调用线程Java栈的帧中 。只有一个线程能总是访问那些局部变量, 即调用方法的线程 。
像方法区和堆一样, Java栈和帧在内存中也不必是连续的 。帧可以分布在连续的栈里,也可以分布在堆里, 或者二者兼而有之。 表示Java栈和栈帧的实际数据结构由虚拟机的实现者决定, 某些实现允许用户指定Java栈的初始大小和最大最小值。
局部变量表
局部变量表存放了编译期可知的各种基本数据类型(boolean、 byte、 char、 short、 int、float、long、 double)、对象引用(reference类型,它不等同于对象本身,根据不同的虚拟机实现, 它可能是一个指向对象起始地址的引用指针, 也可能指向一个代表对象的句柄或者其他与此对象相关的位置) 和 retunAddress类型 (指向了一条字节码指令的地址)。
局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。Java栈帧的局部变量表被组织为一个以字长为单位,从0开始计数的数组。虚拟机通过索引定位的方式使用局部变量表, 编译器首先按声明的顺序把这些参数放入局部变量数组。 字节码指令通过从0开始的索引来使用其中的数据。类型为int、 float、 reference和returnAddress的值在数组中只占据一项, 而类型为byte、 short和char的值在存入数组前都将被转换为int值, 因而同样占据一项。 但是类型为long和double的值在数组中占据连续的两项。
在访问局部变量中的long和double值的时候, 只需取出连续两项中第一项的索引值。 例如某个long值占据第3、 4项, 那么指令会取索引为3的long值。 局部变量区的所有值都是字对齐的, long和double这样占据两项数组元素的值同样可以起始于任何索引 。
下面通过两个例子及图示说明下局部变量区:
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;
}
如果是实例方法(非 static的方法), 那么局部变量表中第 0位素引的 Slot默认是用于传递方法所属对象实例的引用, 在方法中可以通过关键字 “this'' 来访问这个隐含的参数。其余参数则按照参数表的顺序来排列,占用从1开始的局部变量Slot。
操作数栈
和局部变量表一样,操作数栈也被组织成一个以字长为单位的数组。可以把操作数栈理解为存储计算时,临时数据的存储区域。虚拟机在操作数栈中存储数据的方式和在局部变量区中是一样的。
但和前者不同,它不是通过索引来访问的,而是通过压栈和出栈来访问的。操作数栈的毎一个元素可以是任意的 Java数据类型, 32位数据类型所占的栈容量为1, 64位数据类型所占的栈容量为2。
Java 虚拟机的指令是从操作数栈中而不是从寄存器中取得, 因此它的运行方式是基于栈的而不是基于寄存器的 。
虚拟机把操作数栈作为它的工作区——大多数指令都要从这里弹出数据, 执行运算, 然后把结果压回操作数栈。
动态链接
毎个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用, 持有这个引用是为了支持方法调用过程中的动态连接。 Class文件的常量池中存有大量的符号引用, 字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。 这些符号引用一部分会在类加载阶段或第一次使用的时候转化为直接引用,这种转化称为静态解析。 另外一部分将在毎一次的运行期间转化为直接引用 , 这部分为动态连接。
参考:
标签:操作数,Java,虚拟机,局部变量,线程,方法,运行 From: https://blog.51cto.com/u_6947107/7594411