一,虚拟机栈引文
- 虚拟机栈是线程私有的,虚拟机栈的生命周期和线程是相同的;
- 每创建一个线程,虚拟机就会为这个线程创建一个虚拟机栈;
- 每调用一个方法就会为每个方法生成一个栈帧,用来存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法被调用和完成的过程,都对应一个栈帧从虚拟机栈上入栈和出栈的过程。
它主管Java程序的运行,它保存方法的局部变量、部分结果、并参与方法的调用和返回。
虚拟机栈的优点
- 栈访问速度仅次于PC寄存器;
- 只有入栈、出栈两个操作;
- 不存在垃圾回收问题;
二,运行原理
虚拟机栈只有两个操作,压栈和出栈,遵循先进后出原则;在一条线程中,一个时间点上,只会有一个活动的栈帧;在方法中调用其他方法,对应的新的栈帧会被创建出来;执行引擎运行的字节码指令只针对当前栈帧进行操作。
三,栈与堆关系
栈是运行时的单位,解决程序运行问题,即程序如何执行,处理数据。
堆是存储的单位,解决的是数据存储的问题。
四,栈帧
栈帧是虚拟机栈的基本单位,对应一次次的方法调用。它包括局部变量表、操作数栈、动态链接、方法返回地址和附加信息几部分。
4.1,局部变量表 Local Variables
主要用于存储方法参数和定义在方法体内的局部变量,即保存基础数据类型的值和对象以及基础数据的引用。
局部变量表中的变量只在当前方法调用中有效,其所需容量大小在编译期确定下来。在方法运行期间不会改变其大小。当方法调用结束,随着栈帧的销毁,局部变量表也会随之销毁。
栈帧中,与性能调优关系最为密切。在方法执行时,虚拟机使用局部变量表完成方法的传递。局部变量表中的变量也是重要的垃圾回收根节点。
4.1.1 局部变量表的基本存储单元:Slot
JVM 会为局部变量表中的每一个 slot 分配一个访问索引,通过该索引可成功访问到局部变量表中指定的局部变量值。如果当前帧是由构造方法或者实例方法创建的(非 static 方法),该对象引用 this 将会存放在 index=0 的 slot 处。
静态方法不能调用this,因为this不存在当前局部变量表中。
静态变量与局部变量的对比
类变量有两次初始化机会:
①链接阶段的准备阶段:为类变量static分配内存空间并设置其初始值;
②初始化阶段:显示设置类变量初始值。
局部变量表不存在系统初始化的过程。
定义了局部变量必须人为初始化,否则无法使用。
4.2 操作数栈 Operand Stack
操作数栈(Operand Stack)也常称为操作栈,它是一个后入先出栈。
同局部变量表一样,操作数栈的最大深度也在编译的时候写入到 Code 属性的 max_stacks 数据项中。在做算术运算的时候是通过操作数栈来进行的,又或者在调用其他方法的时候是通过操作数栈来进行参数传递的。
主要用于保存计算过程的中间结果,同时作为计算过程中的变量临时的存储空间。
1,操作数栈是 JVM 执行引擎的一个工作区。新创建的栈帧,其方法的操作数栈的空的。
2,有一个明确的栈深度用于存储值,大小在编译器定义好,保存在 Code 属性为max_stack的值。
3,并非采用索引进行数据访问,而是通过标准的入栈、出栈操作进行数据访问。
4,如果被调用的方法有返回值,其返回值将会被压入当前栈帧的操作数栈中。并更新PC寄存器中下一条需要执行的字节码指令。
5,栈中可存储任意的 java 数据类型。32bit 类型占一个栈单位深度;64bit 两个。
6,虚拟机的解析引擎基于栈的执行引擎。其中的栈指的是操作数栈。
4.3,动态链接 Dynamic Linking:
我们知道Class文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用作为参数。
- 这些符号引用一部分会在类加载阶段或者第一次使用的时候就转化为直接引用,这种转化称为静态解析;
- 另外一部分将在每一次运行期间转化为直接引用,这部分称为动态连接。
动态连接就是将符号引用转换为调用方法的直接引用。
每一个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,java 文件被编译到字节码文件中,所有的变量和方法引用都作为符号引用保存在 class 文件的常量池里。
4.4,方法返回地址 Return Address
存放调用该方法的PC寄存器的值。
一个方法结束后都要返回到该方法被调用的位置。
- 方法正常退出:调用者的PC寄存器的值作为返回地址,即调用该方法的指令的下一条指令的地址。
- 方法异常退出:返回地址要通过异常表来确定,栈帧中一般不保存这部分信息。
方法退出的过程实际上就等同于把当前栈帧出栈,因此退出时可能执行的操作有:恢复上层方法的局部变量表和操作数栈,把返回值(如果有的话)压入调用者栈帧的操作数栈中,调整PC计数器的值以指向方法调用指令后面的一条指令等。
动态链接、静态链接:
- 静态链接:当一个字节码文件被装载紧 JVM,如果被调用的目标方法在编译器可知,且运行期保持不变。
- 动态链接:被调用的方法在编译器无法被确定下来。
虚方法、非虚方法:
- 非虚方法:在编译器就确定了具体的调用版本。比如静态方法、私有方法、final方法、实例构造器、父类方法。
- 虚方法:其它自定义的方法。
虚方法表 Virtual method table:
每个类中都有一个虚方法表,表中存放各个方法的实际入口。
在面向对象编程中,会频繁用到动态分派,如果在每次动态分派过程中都要重新在类的方法元数据中搜索合适的目标的话就可能影响到执行效率。
目的:提高性能。jvm采用在类的方法区建立一个虚方法表来实现(非虚方法不会出现在表中),使用索引表来代替查找。
创建:在类加载的链接阶段被创建并开始初始化,类的变量初始值准备完成之后,JVM会把该类的方法表也初始化完毕。
4.5,附加信息:
对程序调试提供支持的信息。可没有。
评论区