运行时数据区
每个线程:独立包括程序计数器、虚拟机栈、本地方法栈
线程间共享:堆、本地方法区(或称堆外内存)
程序计数器(PC寄存器)
作用:用来存储指向下一条指令的地址,也就是即将要执行的指令代码,由执行引擎读取下一条指令
特点:即没有GC(GarbageCollection),也不会OOM(OutOfMemory)
使用PC寄存器存储字节码指令地址的作用?
因为CPU需要不停的切换各个线程,这时候切换回来以后,需要知道从哪开始继续执行。
JVM的字节码解释器(执行引擎)就需要通过改变PC寄存器的值来确定下一条应该执行什么样的字节码指令。
PC寄存器为什么会被设定为线程私有?
为了能够准确记录各个线程正在执行的当前字节码指令地址,最好的办法是为每一个线程都分配一个PC寄存器,这样各个线程之间便可以独立计算,从而不会出现相互干扰的情况。
由于CPU时间片轮限制,众多线程在并发执行过程中,任何一个确定的时刻,一个CPU或一个内核,只会执行某个线程中的一条指令
如何保证分毫不差?
每个线程在创建后,都会产生自己的PC寄存器和栈帧,PC寄存器在各个线程之间互不影响
虚拟机栈
内存中的栈和堆
栈是运行时的单位,而堆是存储的单位
To be specific,栈解决程序的运行问题,即程序如何执行,或者说如何处理数据。堆解决的是数据存储的问题,即数据怎么放,放在哪儿
Java虚拟机栈
每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧(栈中存储数据的单位),对应着一次次Java方法的调用
- 是线程私有的
- 生命周期和线程一致
- 作用:主管Java程序的运行,它保存方法的局部变量(8种基本数据类型,对象的引用地址),部分结果,并参与方法的调用和返回
特点
- 栈是一种快速有效的分配存储方式,访问速度仅次于程序计数器
- 每个方法的执行,伴随着入栈、出栈操作
- 对于栈来说不存在垃圾回收问题(GC),但是存在OOM的问题
问题
1.开发中遇到的异常有哪些?
Java虚拟机规范允许Java栈的大小是固定不变的或者是动态的。
- 固定大小:超过容量,抛出StackOverFlowError异常
- 设置栈的大小:-Xss1m
- 动态:在尝试拓展的时候无法申请到足够的内存,抛出OutOfMemoryError的异常
栈的存储单位
- 每个线程都有自己的栈,栈中的数据都是以栈帧的格式存在
- 在这个线程上正在执行的每个方法都各自对应一个栈帧(Stack Frame)
- 栈帧是一个内存区块,是一个数据集,维系着方法执行过程中的各种数据信息
栈的执行原理
- 在一条活动线程中,一个时间点上,只会有一个活动的栈帧,即只有当前正在执行的方法的栈帧是有效的,这个栈帧被称为“当前栈帧”,与当前栈帧对应的方法就是“当前方法”,定义这个方法的类就是“当前类”
- 执行引擎运行的所有字节码指令只针对当前栈帧进行操作
- 如果该方法中调用了其他方法,对应的新的栈帧会被创建出来,放在栈的顶端,成为新的当前帧
栈帧的内部结构
五部分:局部变量表、操作数栈、动态链接、方法返回地址、一些附加信息
局部变量表
- 最基本的存储单元是Slot(变量槽)。32位以内的类型只占用一个slot(包括returnAddress类型),64位的类型(long和double)占用两个slot
- 定义为一个数字数组(index由0到-1),主要用于存储方法参数和定义在方法体内的局部变量。这些数据类型包括各类基本数据类型、对象引用、returnAddress类型
- 由于局部变量表是建立在线程的栈上,是线程的私有数据,因此不存在数据安全问题
- 局部变量表所需的容量大小是编译期确定下来的,并保存在方法的Code属性的maximum local variables数据项中,在方法运行期间是不会改变局部变量表的大小的
- 如果当前帧是由构造方法或者实例方法创建的,那么该对象引用this将会放在index为0的slot处,其余的参数按照参数表顺序继续排列
操作数栈
在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据。我们说Iava虚拟机的解释引擎是基于栈的执行引擎,其中的栈就是操作数栈
- 主要用于保存计算过程中的中间结果,同时作为计算过程中变量临时的存储空间
- 数组实现。每一个操作数栈都会有一个明确的栈深度用于存储数值,其所需的最大深度在编译期就定义好了,保存在方法的Code属性中,为max_stack的值
动态链接
指向运行时常量池的方法引用
静态链接vs动态链接
- 静态链接(早期绑定):被调用的方法在编译期可知
- 动态链接(晚期绑定):被调用的方法在编译期无法确定
动态类型语言:判断变量值的类型信息,变量没有类型信息,变量值才有类型信息
JS:var name = ‘hello’;
Python:info = 12.1
静态类型语言:
Java:String name = “world”;
方法返回地址
存放调用该方法的pc寄存器的值(方法A调用方法B,当方法B执行完出栈,根据引用继续执行方法A)
一些附加信息
例如,对程序调试提供支持的信息
相关面试题
- 举例栈溢出的情况
StackOverFlow,通过-Xss设置栈的大小:OOM
- 调整栈大小,能保证不出现溢出嘛?
不能,举例:无穷循环
- 分配的栈内存越大越好嘛?
不是,挤占其他空间
- 垃圾回收是否会涉及到虚拟机栈?
不会,虚拟机栈只有OOM,没有GC
本地方法(Native Method)
定义:该方法的实现由非Java语言实现,用native关键字修饰
作用:融合不同的编程语言为Java所用,它的初衷是融合C/C++程序
本地方法栈(Native Method Stack)
- Java虚拟机栈用于管理Java方法的调用,而本地方法栈用于管理本地方法的调用
- 允许被实现成固定或者是可动态拓展的内存大小
- 本地方法栈中登记本地方法,在执行引擎执行时加载本地方法库
- 在Hotspot JVM中,直接将本地方法栈和虚拟机栈合二为一