JVM虚拟机栈

虚拟机栈

每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个栈帧,对应一次次java方法的调用(线程私有
生命周期与线程一致。
作用:主管java线程的运行,保存局部变量(8种基本数据变量,对象引用地址)、部分结果,并参与方法的调用和返回。

对于栈来说不存在垃圾回收。

栈的存储单位(栈帧)

栈帧与方法一对一,进栈调用,出栈结束。
JVM虚拟机栈_第1张图片
java两种返回函数的方式:return指令正常返回、抛异常。两种方式都会导致栈帧被弹出

栈帧中存储着:

  • 局部变量表(Local Variable)
  • 操作数栈(Operand Stack)(或表达式栈)
  • 动态链接(Dynamic Linking)(或指向运行时常量池的方法引用)
  • 方法返回地址(或方法正常退出、异常退出的定义)
  • 一些附加信息
    JVM虚拟机栈_第2张图片

局部变量表(Local Variables)

  1. 定义数字数组,主要存储方法参数和定义在方法体内的局部标量,这些数据类型有 基本数据类型、对象引用(reference)、returnAddress类型。
  2. 由于局部变量建立在线程的栈上,是线程的私有数据,不存在数据安全问题
  3. 局部变量所需的大小是在编译期确定下来的,并保存在方法的code属性maximun local variables。在方法运行期间不会改变局部变量表的大小。

局部变量表是栈帧中最大的部分,与性能调优关系密切。
局部变量表中的变量是重要的垃圾回收根节点,只要被局部变量表中直接或间接应用都不会被回收。

solt(变量槽)

solt 局部变量表的基本储存单位,32位以内的类型只占用一个solt(包括 returnAddress),64位类型(long,double)占用两个solt。

如果当前帧是由构造方法或者实列方法创建的(非静态),那么该对象引用的this将会存放在index为0的slot处。

solt重复利用:栈帧中的局部变量中的槽位是可以重复利用,如果一个局部变量过了其它作用域,那么在其作用域之后新的局部变量就很有可能会利用重复过期局部标量的槽位,从而达到节省资源的目的。

操作数栈

用数组或链表实现,编译时长度确定。
操作数栈在方法执行中,根据字节码指令,往栈中写入数据或读出数据。
java虚拟机的解释引擎是基于操作数栈的执行引擎。
操作数栈主要用于保存计算过程的中间结果,同时作为计算过程中变量的临时存储空间。
操作数栈数据访问是通过入栈出栈,而并非访问索引的方式。
如果被调用的方法具有返回值,其返回值将会被压入当前栈帧的操作数栈中。

栈顶缓存

将栈顶元素全部缓存在物理cpu的寄存器中,以此降低对内存的读写次数,提高执行引擎的效率。

动态链接(指向运行时常量池的方法引用)

在字节码文件中,所有的变量和方法引用都作为符号引用(#6)保存在class文件的常量池里。一个方法调用另一个方法时就是通过常量池中指向方法的符号来表示。动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用。
JVM虚拟机栈_第3张图片

方法的调用

在JVM中,将符号引用转换为调用方法的直接引用与方法得绑定机制有关。

  • 静态链接:
    当字节码进入JVM内部时,如果被调用的目标方法在编译器可知,且运行期保持不变。这种情况将调用方法得符号引用转换为直接引用称之为静态链接。
  • 动态链接:
    如果被调用的方法在编译期无法确定下来,就是说只能在程序运行期将调用方法的符号引用转换为直接引用。具有动态性,称之为动态链接。
  • 非虚方法:
    如果方法在编译期就确定了具体的调用版本,这个版本在运行时是不可变的,称之为虚方法。(静态方法、私有方法、final方法,实例构造器,父类方法)。不涉及多态的形式
  • 虚方法:
    在编译期无法确定此方法是哪一个具体类的方法,在程序运行期根据实际类型才能确定

虚拟机调用指令:

  1. invokestatic:调用静态方法
  2. invokespecial:调用 < init>()方法,私有父类方法,
  3. invokevirtual:调用虚方法
  4. invokeinterface:调用接口
  5. invokedynamic:动态解析需要调用的方法,然后执行(lambda表达式)

(1)(2)调用的是非虚方法。
(3)(4)除final修饰的方法,调用的是虚方法

方法返回地址(return Address)

存放调用方法的PC寄存器的值。
方法调用无论以哪种方式退出,在方法退出后都会返回到该方法被调用的位置。方法正常退出时,调用者的PC计数器的值作为返回地址,即该方法的下一条指令的地址。异常退出的:返回地址要通过异常表来确定,栈帧中一般不保存这部分信息。

正常完成出口与异常完成出口(例没有用try,catch处理异常)的区别是:异常完成出口不会给上层调用者产生任何返回值,异常信息存储在异常处理表中。

栈的相关面试题

  1. 举例栈溢出的情况(StackOverflowError):
    栈帧中最大存储部分是局部表量表,当局部变量表过大是容易造成栈溢出,举例main无限递归调用自己。解决:通过 -Xss 设置栈的大小、OOM

  2. 调整栈的大小,就能保证栈不出现溢出吗?
    不能保证,比如无限递归只能保证栈溢出晚一点

  3. 分配的栈内存越大越好吗?:
    不,内存有限,栈内存大了,其它线程分得就少了

  4. 垃圾回收是否会涉及到虚拟机栈?:不会!!!

  5. 方法中定义的局部变量是否会线程安全?:
    只有一个线程操作此数据,则线程安全。
    如果有多个线程操作此数据,则此数据是共享数据,若不考虑同步机制的话,则存在线程安全问题。
    JVM虚拟机栈_第4张图片

你可能感兴趣的:(JVM,jvm,java)