虚拟机栈

1. 概述

  • 虚拟机栈和线程是紧密联系的,没创建一个线程就会对应创建一个Java栈,所以Java栈是线程私有的。每个栈中会包含多个栈帧,一个栈帧对应一个方法,方法的执行和结束对应着栈帧的入栈和出栈。
  • 栈帧中包含局部变量表、操作数栈、动态链接、返回地址以及一些附加信息(例如锁)。在编译程序代码的时候,栈帧中需要多大的局部变量表,多深的操作数栈都已经完全确定了,并且写入到了方发表的Code属性中,因此以及栈帧需要分配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于具体的虚拟机实现
  • 存放于栈中的东西:8种基本类型的变量+对象的引用变量+实例方法
  • 栈内存:Java虚拟机规范允许Java栈的大小是动态的或者是固定不变的。
    • 如果采用固定大小的Java虚拟机栈,那每一个线程的栈容量会在线程创建时就确定,当超出栈的容量后就会抛出一个StackOverFlowError错误。
    • 如果采用动态扩展的虚拟机栈,那么如果尝试扩展时无法申请到足够的内存,那么就会抛出一个OutOfMenoryError错误。

2. 局部变量表

  • 局部变量表用数组实现,是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。
  • 在编译期间就已经确定了方法所需要分配的局部变量表的最大容量并保存在方法Code属性的maximum local variables数据项中,在运行期间不会改变局部变量表的大小。
  • 关于变量槽slot
    • slot是局部变量表的最小容量单位。
    • 在局部变量表中,32位以内的类型占有一个slot,包括引用数据类型,64位的类型(long和double)占据两个slot。
    • byte、short、char在存储前会被转换为int,boolean也会被转换为int。
    • JVM会为局部变量表中的每一个slot都分配一个访问索引,通过这个索引即可成功访问到局部变量表中指定的局部变量值。对于占两个slot的64位数据类型而言,JVM禁止单独访问其中的某一个。
    • 如果当前帧是由构造方法或者实例方法创建,那么该对象引用this将会放在index为0的slot处。
    • 局部变量表中的槽位是可以重用的

3. 操作数栈

  • 顾名思义,它是由一个栈结构来实现的,栈的最大深度也是在编译期就确定的并且被保存到方法的Code属性的max_stack数据项中。
  • 操作数栈中的元素可以是任意的Java数据类型32位类型数据占的栈容量为1,,4位类型数据占的栈容量为2.
  • 如果被调用的方法带有返回值的话,其返回值将会被压入当前栈帧的操作数栈中
  • 操作数栈主要用于保存计算过程的中间结果,同时作为计算过程中变量的临时存储空间
  • 操作数栈的具体说明图解:

4. 动态连接

每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接

  • 运行时常量池位于方法区,字节码中的常量池结构如下:
字节码中的方法调用指令就是以常量池中指向方法的符号引用作为参数。这些符号引用一部分会在类加载阶段或者第一次使用的时候就转化为直接引用,即静态解析。另一部分将在每次运行期间转化为直接引用,这部分称为动态连接。

5.方法调用

方法调用并不等同于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本,即调用哪一个方法。字节码文件中的符号引用给Java带来了强大的动态扩展能力,但也使得方法的调用过程变得相对复杂。

  • 当一个字节码文件被装载进JVM内部时,如果被调用的目标方法在编译期可知,且运行期保持不变,这类方法的调用称为静态解析。如果被调用的方法在编译期无法确定下来,也就是说,只能够在程序运行期间将调用方法的符号引用转换为直接引用,这类方法的嗲用称为动态连接
  • 在Java语言中符合编译期可知,运行期不可变这个要求的方法主要包括静态方法和私有方法两大类,前者与类型直接关联,后者在外部不可被访问,这两种方法各自的特点决定了它们都不可能通过继承或别的方式重写其他版本,因此是不可变的,适合于静态解析。
  • 重点掌握的指令
   普通调用指令:
   1.invokestatic:调用静态方法,解析阶段确定唯一方法版本;
   2.invokespecial:调用<init>方法、私有及父类方法,解析阶段确定唯一方法版本;
   3.invokevirtual:调用所有虚方法;
   4.invokeinterface:调用接口方法;
   动态调用指令(Java7新增):
   5.invokedynamic:动态解析出需要调用的方法,然后执行 .
   前四条指令固化在虚拟机内部,方法的调用执行不可人为干预,而invokedynamic指令则支持由
用户确定方法版本。
   其中invokestatic指令和invokespecial指令调用的方法称为非虚方法

   其中invokevirtual(final修饰的除外,JVM会把final方法调用也归为invokevirtual指
令,但要注意final方法调用不是虚方法)、invokeinterface指令调用的方法称称为虚方法。

只要被invokestatic指令和invokespecial指令调用的方法都可以在解析阶段确定唯一的调用版本,符合这个条件的有静态方法、私有方法、实例构造器、父类方法,它们称为非虚方法。注意,被final修饰的方法也是非虚方法。其他方法则是虚方法

6. 方法返回地址

当一个方法开始执行后,只有两种方式可以退出这个方法即正常完成出口和异常完成出口。无论采用哪种方式退出,在退出之后,都需要返回到方法被调用的位置,程序才能继续执行。当正常退出时,在方法返回地址中就存放调用该方法的PC寄存器的值,而异常退出时,需要通过异常处理器表来确定返回地址。


本文章使用limfx的vscode插件快速发布