今天继续给大家分享JVM系列的相关知识,今天介绍一下虚拟机栈的介绍。
1、虚拟机栈的概念
虚拟机栈也称为Java栈,Java每个main方法被执行的时候,JVM都会同步创建一个栈帧(Stack Frame),通过存储局部变量表、操作数栈、动态链接、方法出口等信息来支撑和完成方法的执行。栈帧就是虚拟机栈中的子单位。栈其实只有入栈和出栈两种操作。
栈的操作
入栈:每一次方法调用都会有一个对应的栈帧被压入栈中,也成为压栈。
出栈:方法调用结束后,弹出,也成为弹栈。
2、虚拟机栈的特点
- 先进后出的原则
- 线程私有的,它的生命周期和线程保持一致,随线程而生,随线程而灭。
- 线程请求的栈深度大于虚拟机所允许的最大深度,会抛出StackOverflowError栈溢出异常
- 虚拟机栈可以动态扩展,如果扩展的时候无法申请得到到足够的内存,就会抛出OutOfMemoryError异常
运行时常见的异常
- NullPointerException - 空指针引用异常
- ClassCastException - 类型强制转换异常
- IllegalArgumentException - 传递不合法参数异常
- ArithmeticException - 算术计算异常
- IndexOutOfBoundsException - 下标越界异常
- NumberFormatException - 数字格式异常
- UnsupportedOperationException - 不支持的操作异常
3、栈帧
Java中每一个方法从调用开始到执行完成的过程,其实都对应着一个栈帧在虚拟机线程里面从入栈到出栈的过程。
4、栈帧的组成
栈帧由局部变量表、操作数栈(Operand Stack)、动态链接(Dynamic Linking)、方法返回地址(Return Address)和一些附加信息(对程序调试提供支持的信息)组成。
说明:
- 在活动线程中,只有位于栈顶的栈帧叫做当前栈帧,也是正在执行的方法
- Java执行引擎运行的所有字节码指令其实都只针对当前栈帧进行操作
4.1 局部变量表
主要存放了编译期明确的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型,它不是针对对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。
4.2 操作数栈
操作数主要作为方法调用的中转站使用,主要用来存放Java方法执行过程中产生的中间计算结果和计算过程中产生的临时变量。
4.3 动态链接
动态链接主要支撑一个Java方法需要调用其他方法的场景。当 Java 源文件被编译成字节码文件时,所有的变量和方法引用都作为符号引用(Symbilic Reference)保存在Class 文件的常量池里面。当一个Java方法要调用其他Java方法,需要将常量池中指向方法的符号引用转换为其在内存地址中的直接引用。
动态链接作用:其实就是将符号引用转换为调用方法的直接引用。
4.4 方法返回地址
Java方法开始执行后,退出这个方法的方式:正常退出、异常退出
- 正常退出:执行引擎遇到方法返回的字节码指令,这时候可能会有返回值传递给上层的方法调用者。 正常退出时调用PC计数器的值可以作为返回地址。
- 异常退出:在方法执行过程中遇到异常,且异常没有在方法体内得到处理,返回地址要交给异常处理表来决定如何处理。
说明:方法退出之后,都需要返回到方法被调用的原始位置,程序才能继续执行
5、栈的优缺点
优点:
栈帧内数据共享:一个栈帧中内存数据共享,不同栈帧之间数据不共享,这样可以减少内存消耗
存储速度:栈帧存取数据快,仅次于寄存器,
- 编译的时候就分配好了内存,运行过程中不需要申请内存大小,节约时间成本。
- 栈是机器提供的数据结构,计算机会分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,效率高
- 相比较堆来说,访问效率高
缺点
灵活性差:栈在运行过程中,不能动态的去申请内存、程序可能会报错。
6、内存中栈和堆的对比
栈属于运行时的单位主要解决程序如何执行的问题,堆属于存储的单位主要是用来解决数据的存储问题。
- 堆是运行时数据区较大的一块,所以Java的对象基本都放在堆空间。
- 栈主要用来存放基本数据类型的局部变量、引用数据类型的对象的引用