当前位置: 首页>编程语言>正文

JVM(八) JVM Stacks

JVM Stacks:

JVM Stacks :线程私有 先看一下官方文档当中的描述:

JVM(八) JVM Stacks,image.png,第1张

JVM(八) JVM Stacks,image.png,第2张 jvm 栈是描述java方法执行的内存模型,它的生命周期和线程相同,每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

栈帧

栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构。每个方法对应一个栈帧。具体类型结构如图:

JVM(八) JVM Stacks,image.png,第3张

局部变量表

局部变量表(Local Variable Table)是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量

JVM(八) JVM Stacks,image.png,第4张 局部变量表以变量槽(Variable Slot)为最小单位。特点有以下几个:

 1.虚拟机没有明确指定slot应该占的大小,只是很有导向性地说到每个Slot都应该能存放一个boolean、byte、char、short、int、float、reference或returnAddress类型的数据。一般是32位。
 2.它允许Slot的长度可以随着处理器、操作系统或虚拟机的不同而发生变化,在64位操作系统中采用填充对齐的方法使其一致
 3.long、double这种64位的数据会被分配两个连续Slot空间
 4.连续空间可以通过较小的索引来访问数据。

我们知道在java中有两种方法,一种是类方法,一种是实例方法。 在类方法调用中,所有参数都从局部变量0开始在连续的局部变量中传递。 在实例方法调用中,局部变量0始终指向的是该实例对象,也就是this。也就是说真实的参数是从局部变量1开始存储的。

操作数栈

操作数栈(Operand Stack)也常称为操作栈,是一个后入先出的栈(LIFO LAST IN FIRST OUT).操作数栈的每一个元素可以是任意的Java数据类型,包括long和double。32位数据类型所占的栈容量为1,64位数据类型所占的栈容量为2。在方法执行的任何时候,操作数栈的深度都不会超过max_stacks数据项中设定的最大值。

JVM(八) JVM Stacks,image.png,第5张 让我们通过两段段代码来理解一下:

1.事先我们得准备一个plugin:jclasslib Bytecode Viewer。然后通过show bytecode with jclasslib 打开文件。

JVM(八) JVM Stacks,image.png,第6张 代码 A

	public static void main(String[] args) {
        int i = 8;
        i = i++;    
        System.out.println(i);
    }
    //结果为8

代码 B

	public static void main(String[] args) {
        int i = 8;
        i = ++i;    
        System.out.println(i);
    }
    //结果为9

A 本地方法表

JVM(八) JVM Stacks,image.png,第7张 代码A 点开 方法-code 字节码文件:

JVM(八) JVM Stacks,image.png,第8张 可以直接点击指令进入文档对应位置进行阅读。这边讲解几条

bipush

JVM(八) JVM Stacks,image.png,第9张 (压栈)

istore_< n>

栈顶元素出栈,放入局部变量表号位

JVM(八) JVM Stacks,image.png,第10张

iload_< n>

JVM(八) JVM Stacks,image.png,第11张 将局部变量表 n 号位的值 推入操作数栈

iinc

JVM(八) JVM Stacks,image.png,第12张 根据索引将局部变量表索引位的数增加常量

我们来根据解释一下 代码A:

JVM(八) JVM Stacks,image.png,第13张

1.bipush 8       将8压入操作数栈   , **此时栈内值为8**
2.istore_1 8 出栈 ,放入局部变量表1号位。**此时栈空,局部变量表1号位8**
3.iload_1 将局部变量表1号位的数压栈。 **此时局部变量表1号位 8 ,操作数栈8**
4.iinc 1 by 1 将局部变量表1号位 +1  **此时局部变量表1号位 9 ,操作数栈8**
5.istore_1 8 出栈 ,放入局部变量表1号位。**此时栈空,局部变量表1号位8**
7.iload_1 将局部变量表1号位的数压栈。 **此时局部变量表1号位 8 ,操作数栈8**

代码B:

JVM(八) JVM Stacks,image.png,第14张

1.bipush 8       将8压入操作数栈   , **此时栈内值为8**
2.istore_1 8 出栈 ,放入局部变量表1号位。**此时栈空,局部变量表1号位8**
3.iinc 1 by 1 将局部变量表1号位 +1  **此时局部变量表1号位 9 ,操作数栈空**
4.iload_1 将局部变量表1号位的数压栈。 **此时局部变量表1号位 9 ,操作数栈9**
5.istore_1 9 出栈 ,放入局部变量表1号位。**此时栈空,局部变量表1号位9**
……

动态连接

JVM(八) JVM Stacks,image.png,第15张 从网上偷张图(https://www.cnblogs.com/caca/p/jvm_stack_frame.html)。

简而言之,每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking)。字节码中的方法调用会用常量池中指向方法的符号引用作为参数,在类加载阶段就转变成直接引用的就是静态解析,在运行期间转换的为动态连接。

JVM提供了五种invoke指令:

invokestatic:调用静态方法。
invokespecial:调用实例构造器<init>方法、私有方法和父类方法。
invokevirtual:(自带多态)调用所有的虚方法。
invokeinterface:调用接口方法,会在运行时再确定一个实现此接口的对象		 
invokedynamic:(lambda表达式函数式调用的时候多用到这条) 先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法,在此之前的4条调用指令,分派逻辑是固化在Java虚拟机内部的,而invokedynamic指令的分派逻辑是由用户所设定的引导方法决定的。

程序运行正常的返回处理

JVM(八) JVM Stacks,image.png,第16张 简而言之,将其他栈帧的返回值推入调用方操作数栈,适当修改pc使得跳过调用的指令。


https://www.xamrdz.com/lan/5tx1924476.html

相关文章: