JVM运行时数据区
Java运行时数据区可以细分为五个模块:栈,堆,寄存器,方法区和本地方法栈
在Java8之后,方法区改成了元空间,有一些区域是线程私有的,有一些是线程共享的
线程私有
程序计数器
程序计数器也叫寄存器,每个线程启动的时候,都会创建一个 PC(Program Counter,程序计数器)寄存器。PC 寄存器里保存有当前正在执行的 JVM 指令的地址。
- 程序计数器是一块比较小的内存空间,是当前线程所执行的字节码的行号指示器;
- 字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令;
- 比如分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完。
- 程序计数器是一个在虚拟机中没有规定内存溢出的区域,他的生命周期随着线程的创建而创建,随着线程的结束而死亡
主要作用
- 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制
- 在多线程的情况下,程序计数器用于记录当前线程执行的位置,当线程被切换回来的时候能
恢复上次执行的位置
Java 虚拟机栈
Java虚拟机栈来描述Java方法的内存模型。每当有新线程创建时就会分配一个栈空间,线程结束后栈
空间被回收,栈与线程拥有相同的生命周期。
虚拟机栈异常
线程请求的栈深度大于虚拟机允许的深度抛出StackOverflowError。
如果JVM栈容量可以动态扩展,栈扩展无法申请足够内存抛出OutOfMemoryError
虚拟机栈特点
Java 虚拟机栈是线程私有的,它的生命周期与线程相同(随线程而生,随线程而灭);
如果线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError 异常;
如果虚拟机栈可以动态扩展,如果扩展时无法申请到足够的内存,就会抛出 OutOfMemoryError 异常;
Java 虚拟机栈描述的是 Java 方法执行的内存模型:每个方法执行的同时会创建一个栈帧。对于我们来说,主要关注的栈内存,就是虚拟机栈中局部变量表部分。
栈帧
在虚拟机栈中的元素用于支持虚拟机进行方法调用,每个方法在执行时都会创建一个栈帧存储方法的局部变量表、操作栈、动态链接和方法出口等信息。每个方法从调用到执行完成,就是栈帧从入栈到出栈的过程。
栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构。它是虚拟机运行时数据区中的 java 虚拟机栈的栈元素
局部变量表
局部变量表用于存放方法参数和方法内部定义的局部变量。
局部变量表的容量以变量槽(Variable Slot)为最小单位;
在方法执行过程中,Java 虚拟机是使用局部变量表完成参数值到参数变量列表的传递过程;
操作栈
- 操作数栈是一个后入先出(Last In First Out)栈,方法的执行操作在操作数栈中完成,每一个字节码指令往操作数栈进行写入和提取的过程,就是入栈和出栈的过程;
- 操作数栈的每一个元素可以是任意的 Java 数据类型,32 位数据类型所占的栈容量为 1,64 位数据类型所占的栈容量为 2;
- 当一个方法刚刚执行的时候,这个方法的操作数栈是空的,在方法执行的过程中,通过一些字节码指令从局部变量表或者对象实例字段中复制常量或者变量值到操作数栈中。
动态链接
- 每个栈帧都包含一个指向运行时常量池(JVM 运行时数据区域)中该栈帧所属方法属性的引用;
- 持有这个引用是为了支持方法调用过程中的动态链接。
方法出口信息
方法出口信息代表的是方法执行结束,方法执行结束有两种方式 - 一种方式是执行引擎遇到任意一个方法返回的字节码指令(例如:return),这时候可能会有返回值传递给上层的方法调用者,这种退出方法的方式称为正常完成出口
- 另外一种退出方式是,在方法执行过程中遇到了异常,并且这个异常没有在方法体内得到处理,无论是 Java 虚拟机内部产生的异常,还是代码中使用 throw 字节码指令产生的异常,只要在本方法的异常处理器表中没有搜索到匹配的异常处理器,就会导致方法退出,这种退出方法的方式称为异常完成出口
- 不管哪种方式,栈帧都会被弹出
本地方法栈
- 本地方法栈与虚拟机栈作用相似;
- 区别虚拟机栈为虚拟机执行Java方法服务,本地方法栈为虚本地方法服务;
- 调用本地方法时虚拟机栈保持不变,动态链接并直接调用指定本地方法;
- 本地方法被执行的时候,在本地方法栈也会创建一个栈帧, 用于存放该本地方法的局部变量表、操作数
栈、动态链接、出口信息。 - 本地方法栈在栈深度异常和栈扩展失败时分别抛出StackOverflowError和OutOfMemoryError。