运行时数据区的完整图
Java虚拟机定义了若干种程序运行期间会使用到的运行时数据区,其中有一些会随着虚拟机启动而创建,随着虚拟机退出而销毁。另外一些则是与线程一一对应的,这些与线程对应的数据区域会随着线程开始和结束而创建和销毁。
每个线程:独立包括程序计数器,栈、本地栈
线程共享:堆、对外空间(永久代或元空间、代码缓存)
线程
后台系统线程在Hotspot JVM主要以下几个:
- 虚拟机线程:虚拟机线程:这种线程的操作是需要JVM达到安全点才会出现。这些操作必须在不同的线程中发生的原因是他们都需要JVM达到安全点,这样堆才不会变化。这种线程的执行类型包括"stop-the-world"的垃圾收集,线程栈收集,线程挂起以及偏向锁撤销。
- 周期任务线程:周期任务线程:这种线程是时间周期事件的体现(比如中断),他们一般用于周期性操作的调度执行。
- GC线程:这种线程对在JVM里不同种类的垃圾收集行为提供了支持
- 编译线程:这种线程在运行时会将字节码编译成到本地代码
- 信号调度线程:这种线程接收信号并发给JVM,在它内部通过调用适当的方法进行处理
1.程序计数器(PC寄存器)
PC寄存器用来存储指向下一条指令的地址,也即将要执行的指令代码。由执行引擎读取下一条指令。
使用PC寄存器存储字节码指令地址有什么用呢?
因为CPU需要不停的切换各个线程,这时候切换回来以后,就得知道接着从哪开始继续执行。
JVM的字节码解释器就需要通过改变Pc寄存器的值来明确下一条应该执行什么样的字节码指令。
2.虚拟机栈
栈是运行时的单位,而堆是存储的单位。
栈解决程序的运行问题,即程序如何执行,或者说如何处理数据。堆解决的是数据存储的问题,即数据怎么放、放在哪儿。
每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈桢,对应着一次次的Java方法调用
虚拟机栈作用
主管Java程序的运行,它保存方法的局部变量(8种基本数据类型、对象的引用地址)、部分结果,并参与方法的调用和返回。
栈的特点
栈是一种快速有效的分配存储方式,访问速度仅次于程序计数器
JVM直接对Java栈的操做只有两个:
1.每个方法执行,伴随着进栈(入栈,压栈)
2.执行结束后的出栈操作
对于栈来说不存在垃圾回收 GC OOM
栈中可能出现的异常
- Java虚拟机规范允许Java栈的大小是动态的或者是固定不变的。
如果采用固定大小的Java虚拟机栈,那每一个线程的Java虚拟机栈容量可以在线程创建的时候独立选定。如果线程请求分配的栈容量超过Java虚拟机栈允许的最大容量,Java虚拟机将会抛出一个stackoverflowError异常。
如果Java虚拟机栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈,那Java虚拟机将会抛出一个outofMemoryError异常。
设置栈内存大小
使用参数-Xss 选项来设置线程的最大栈空间,栈的大小直接决定了函数调用的最大可达深度。-Xss1m
栈的存储单位
1.每个线程都有自己的栈,栈中的数据都是以栈帧(Stack Frame)的格式存在
2.在这个线程上正在执行的每个方法都各自对应一个栈帧
3.栈帧是一个内存区块,是一个数据集,维系着方法执行过程中的各种数据信息
栈运行原理
1.Jvm直接对Java栈的操作只有两个,压栈和出栈,先进后出,后进先出
2.在一条活动线程中,只有一个活动的栈帧
栈帧的内部结构
1.局部变量表
2.操作数栈
3.动态链接(或指向运行时常量池的方法引用)
4.方法返回地址(或方法正常退出或者异常退出的定义)
5.一些附加信息
-
局部变量表
定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量,这些数据类型包括各类基本数据类型、对象引用,以及returnAddress类型
由于局部变量表是建立在线程的栈上,是线程的私有数据,因此不存在数扶安全问题
局部变量表所需的容量大小是在编译期确定下来的,并保存在方法的code属性的maximum local variables数据项中。在方法运行期间是不会改变局部变量表的大小的。
关于Slot的理解:
局部变量表,最基本的存储单元是Slot(变量槽)
局部变量表中存放编译期可知的各种基本数据类型(8种),引用类型,returnAddress类型的变量
在局部变量表里,32位以内的类型只占用一个slot(包括returnAddress类型),64位的类型(long和double)占用slot
JVM会为局部变量表中的每一个slot都分配一个访问索引,通过这个索引即可成功访问到局部变量表中指定的局部变量值
-
操作数栈
每一个独立的栈帧中除了包含局部变量表以外,还包含一个后进先出(Last-In-First-out)的操作数栈,也可以称之为表达式栈(Expression stack) 。操作数栈,在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据,即入栈(push)/出栈(pop)。某些字节码指令将值压入操作数栈,其余的字节码指令将操作数取出栈。使用它们后再把结果压入栈。
操作数栈,在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据,即入栈(push)/出栈(pop)。
某些字节码指令将值压入操作数栈,其余的字节码指令将操作数取出
操作数栈,主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。
操作数栈就是JVM执行引擎的一个工作区,当一个方法刚开始执行的时候,一个新的栈帧也会随之被创建出来,这个方法的操作数栈是空的。
每一个操作数栈都会拥有一个明确的栈深度用于存储数值,其所需的最大深度在编译期就定义好了,保存在方法的code属性中,为max_stack的值。
题目
举例栈溢出的情况(StackOverflowError)
通过-Xss设置栈的大小
调整栈大小,就能保证不出现溢出吗?
不能
分配的栈内存越大越好吗?
不是
垃圾回收是否会涉及到虚拟机栈?
否
方法中定义的局部变量是否线程安全?
具体问题具体分析
3. 本地方法栈
Java虚拟机栈用于管理Java方法的调用,而本地方法栈用于管理本地方法的调用。
本地方法栈,也是线程私有的