运行时数据区及线程:
内存是硬盘和cpu的中间仓库及桥梁承载着操作系统和应用程序的实时运行。JVM内存布局规定Java在运行过程中内存申请、分配、管理的策略,保证了JVM的高效稳定运行。不同的JVM对于内存的划分和管理机制存在着部分差异。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200915171031585.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzQ3NDYwNjc4,size_16,color_FFFFFF,t_70#pic_center
Java虚拟机定义若干种程序运行期间会使用到的运行时数据区,其中有一些会随着虚拟机启动而创建,随着虚拟机退出而销毁。另外一些是与线程一 一对应,这些与线程对应的数据区域会随着线程开始和结束而创建和销毁。
下图:
灰色是单独线程私有的,红色的为多个线程共享的.
1.每个线程,独立包括程序计数器、栈、本地栈。
2.线程间共享:堆、堆外内存(永久代或元空间、代码缓存)
PC: PC寄存器
VMS: 虚拟机栈
NMS: 本地栈
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200915171902564.png#pic_center)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200915172108164.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzQ3NDYwNjc4,size_16,color_FFFFFF,t_70#pic_center)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200915173200359.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzQ3NDYwNjc4,size_16,color_FFFFFF,t_70#pic_center)
线程
线程是一个程序里的运行单元。JVM允许一个应用有多个线程并行的执行。
在Hotspot JVM里,每个线程都与操作系统的本地线程直接映射。
1.当一个Java线程准备好执行以后,此时一个操作系统的本地线程也同时创建。Java线程执行终止后,本地线程也会回收。
操作系统负责所有线程的安排调度到任何一个可用的CPU上,一旦本地线程初始化成功,就会调用Java线程中的run()方法。
JVM里主要有以下几个:
**虚拟机线程:**这种线程的操作是需要JVM达到安全点才会出现的。这些操作必须在不同的线程中发生的原因是他们都需要JVM达到安全点,这样堆才不会变化。这种线程的执行类型包括"stop-the-world" 的垃圾收集,线程栈收集,线程挂起以及偏向锁撤销。
**周期任务线程:**这种线程是时间周期事件的体现(比如中断),他们一般用于周期性操作的调度执行
**GC线程:**这种线程对在JVM里不同种类的垃圾收集行为提供了支持
**编译线程:**这种线程在运行时会将字节码编译成到本地代码。
**信号调度线程:**这种线程接受信号并发送给JVM,在它内部通过调用适当的方法进行处理。
==================================
程序计数器(PC寄存器)
PC Register介绍
JVM中的程序计数寄存器(PC)中,Register的命名源于CPU的寄存器,寄存器存储指令相关的现场信息。CPU只有把数据装载到寄存器才能够运行。
JVM中的PC寄存器是对物理PC寄存器的一种抽象模拟
作用:
PC寄存器用来存储指向一条指令的地址,也就是即将要执行的指令代码。由执行引擎读取下一条指令。
它是一块很小的内存空间,几乎可以忽略不计,也是运行速度最快的存储区域
在JVM规范中,每个线程都有它自己的程序计数器,是线程私有的,生命周期与线程的生命周期保持一致。
任何时间一个线程都只有一个方法在执行,也就是所谓的当前方法。程序计数器会存储当前线程正在执行的Java方法的JVM指令地址;
如果是在执行native方法,则是未指定值
它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成
字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令
它是唯一 一个在Java 虚拟机规范中没有规定任何OutotMemoryError情况的区域
举例视图说明:
在main方法中定义三个变量,编译后见下图
左边0-10 就是PC寄存器中 的指令地址(偏移地址)
右边是操作指令
PC寄存器常见问题
1.使用PC寄存器存储字节码指令地址有什么用?
为什么使用PC寄存器记录当前线程的执行地址?
因为CPU需要不停的切换各个线程,这时候切换回来以后,就得知道接着从哪开始继续执行
JVM的字节码解释器需要通过改变PC寄存器的值来明确下一条应该执行什么样的字节码指令
*
2.PC寄存器为什么会被设定为线程私有?
所谓的多线程是在一个特定的时间段内只会执行其中某一个线程的方法,CPU会不停的做任务切换,这样必然导致经常中断或恢复。
为了能够准确的记录各个线程正在执行的当前字节码指令地址,最好为每一个线程都分配一个PC寄存器这样各个线程之间就可以进行独立计算,而不会出现相互干扰的情况。
由于CPU时间片轮限制,众多线程在并发执行过程中,任何一个确定的时刻,一个处理器或者多核处理器中的一个内核,只会执行某个线程中的一条指令。
===============================虚
虚拟机栈
优点:跨平台,指令集小,编译器容易实现。
缺点:性能下降,实现同样的功能需要更多的指令
内存中的栈和堆
栈是运行时的单位。堆是存储的单位
即:栈解决程序的运行问题,即程序如何执行,或说如何处理数据。
堆解决的是数据存储的问题,即数据怎么放、放在哪里
Java虚拟机栈(Java Virtual Machine Stack)
早期也叫Java栈。
每个线程在创建都会创建一个虚拟机栈,在内部保存一个个的栈帧(Stack Frame),对应着一次次的Java方法的调用
它是线程私有的
生命周期和线程一致
作用:主管Java的程序的运行,保存方法的局部变量(8种基本数据类型、对象的引用地址)、部分结果,并参与方法的调用和返回。
栈的优点:
栈是一种快速有效的分配存储方式,访问速度仅次于程序计数器。
JVM直接对Java栈的操作只有两个:
1.每个方法执行,伴随着进栈(入栈、压栈)
2.执行结束后的出栈工作
对于栈来说不存在垃圾回收问题
**
栈中可能出现的异常:**
Java 虚拟机规范允许Java栈的大小是动态的或者是固定不变的
1.如果采用固定大小的Java虚拟机栈,每一个线程的Java虚拟机栈容量可以在线程创建的时候独立选定。如果线程请求分配的栈容量超过Java虚拟机栈允许的最大容量,Java虚拟机将会抛出一个
StackOverflowError 异常(栈溢出)
2.如果Java虚拟机栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈,那Java虚拟机将抛出一个
OutOfMemoryError (OOM) 异常(内存不足)
===========================================
栈中存储的是什么?
每个线程都有自己的栈,栈中的数据都是以栈帧(Stack Frame) 的格式存在。
在这个线程上正在执行的每个方法都各自对应一个栈帧Stack Frame)
栈帧是一个内存区块,是一个数据集,维系着方法执行过程的各种数据信息
栈的运行原理:
JVM直接对Java 栈的操作只有两个,就是对栈帧的压栈和出栈,遵循"先进后出" /“后进先出” 的原则
在一条活动线程中,一个时间点上,只会有一个活动的栈帧。只有当前正在执行的方法的栈帧(栈顶栈帧)是有效的,这个栈帧被称为当前栈帧(Current Frame),与当前栈帧相对应的方法就是当前方法(Current Method) 定义这个方法的类就是当前类(current Class)
执行引擎运行的所有字节码指令只针对当前栈帧进行操作。
如果在该方法中调用了其他方法,对应的新的栈帧会被创建出来,放在栈的顶端,成为新的当前帧.
不同的线程中所包含的栈帧是不允许存在相互引用的,即不可能在一个栈帧之中引用另一个线程的栈帧。
如果当前方法调用了其他方法,方法的返回之际,当前栈帧会传回此方法的执行结果给前一个栈帧,接着虚拟机会丢弃当前栈帧,使得前一个栈帧重新成为当前栈帧。
Java方法有两种返回函数的方式,一种是正常的函数返回,使用return 指令,另一种是抛出异常,不管使用哪种方式,都会导致栈帧被弹出。