JVM从零到一系列:03--运行时数据区【PC寄存器,虚拟机栈】


0x01.运行时数据区概述

1.运行时数据区模型图(整体图)

在这里插入图片描述

2.运行时数据区模型图(详情图)

在这里插入图片描述

3.概述

  • Java虚拟机定义了若干程序运行期间会使用到的运行时数据区:

    • 有一些会随着虚拟机的启动而创建,随着虚拟机退出而销毁。
    • 还有一些与线程一一对应,与线程对应的数据区会随着线程的开始和结束而创建和销毁。
    • 独立的线程:【程序计数器,栈,本地栈
    • 共享的线程:【堆,堆外内存,方法区
  • Java虚拟机与线程:

    • 线程是一个程序运行的单元,JVM允许一个应用有多个线程。
    • HotSpot中,每个线程都与操作系统的本地线程直接映射。当Java线程准备好执行后,此时操作系统的本地线程也同时创建。Java线程结束后,本地线程也会被回收。
    • 本地线程一旦初始化成功,就会调用Java线程中的run()方法。

0x02.程序计数器(PC寄存器)

1.概述

  • JVM中的PC寄存器是对物理PC寄存器对的一种抽象模拟。
  • PC计数器,也称为程序钩子
  • 每个线程都有一个PC寄存器,生命周期与线程的生命周期保持一致。
  • 程序计数器会存储当前线程正在执行的Java方法的JVM指令地址,如果是在执行本地方法,则是未指定的值(undefned)。
  • PC寄存器是唯一一个在Java虚拟机规范中没有规定任何OutOfMenmoryError情况的区域。

2.作用

  • PC寄存器用来存储指向下一条指令的地址,也就是即将要执行的指令的代码,由执行引擎读取下一条指令。

3.使用PC寄存器存储字节码指令地址的作用

  • 因为CPU需要不停的切换各个线程,这时候切换回来以后,就得知道接着从哪开始继续执行。
  • JVM的字节码解释器就需要通过改变PC寄存器的值来明确下一条应该执行什么样的字节码指令。

4.PC寄存器为什么会被设定为线程私有?

  • CPU会不停的做任务切换,这样会导致经常的终端或恢复,为了能够准确地记录各个线程正在执行的当前字节码指令地址,最好的办法是为每一个线程都分配一个PC寄存器。这样各个线程之间便可以进行独立计算,从而不会出现相互干扰的情况。

0x03.虚拟机栈

1.概述

  • Java虚拟机早期也叫Java栈,每个线程在创建时都会创建一个虚拟机栈,其内部保存着一个个的栈帧,对应着一次次Java方法的调用。【是线程私有的
  • 生命周期和线程一致。
  • 虚拟机栈的作用: 主管Java程序的运行,它保存方法的局部变量,部分结果,并参与方法的返回与调用。

2.StackOverFlowError

  • Java虚拟机规范允许Java栈的的大小是动态的或者是固定不变的。
  • 如果采用固定大小的Java虚拟机栈,那每一个线程的Java虚拟机容量可以在线程创建的时候选定。
  • 如果请求分配的栈容量超过Java虚拟机栈允许的最大容量,Java虚拟机就会抛出StackOverFlowError异常。

3.设置栈大小

  • 我们可以在编译的时候使用参数-Xss来设置线程的最大栈空间。
  • 栈大小直接决定了函数调用的最大可达到深度

4.栈的存储结构和运行原理

  • 栈中的数据都是以栈帧的格式存在。
  • 每个方法都对应一个栈帧
  • 栈帧是一个内存区块,是一个数据集,维系着方法执行过程中的各种数据信息。
  • 在一条活动的线程上,在一个时间点上,只会有一个活动的栈帧,这个栈帧是当前栈帧,与这个栈帧对应的方法是当前方法,定义这个方法的类叫做当前类
  • 执行引擎运行的所有字节码指令只针对当前栈帧进行操作。
  • 如果在该方法中调用了其它方法,那么对应新的栈帧会被创建出来,放在栈的顶端,成为新的栈帧。
    在这里插入图片描述

5.栈帧内部结构

  • 栈帧存储的信息:

    • 局部变量表
    • 操作数栈
    • 动态链接。
    • 方法返回地址。
    • 一些附加信息。

(1)局部变量表:

  • 局部变量表也被称为局部变量数组或本地变量表。

  • 定义为一个数字数组,主要用于存储方法参数和定义在方法内部的局部变量。(这些数据包括:基本数据类型,对象的引用,返回地址类型)

  • 局部变量表不存在数据安全性的问题。(是线程的私有数据)

  • 局部变量表所需的容量是在编译时期确定下来的,方法运行时期是不会改变局部变量表的大小的

  • 局部变量表的最基本的存储单元是Slot(变量槽)

    • 局部变量表中32位以内的类型占一个Slot,64位类型占两个Slot。
    • JVM会为局部变量表中的每一个Slot都分配一个访问索引,通过这个索引即可成功访问到局部变量表中指定的局部变量的值。
    • 当一个实例方法被调用时,它的方法参数和方法体内部定义的局部变量将会按照顺序被复制到局部变量表的每一个Slot上。
    • byte,short,char,boolean会在存储前转换为int,占一个Slot。
    • long和double则占两个Slot。
    • 如果当前栈帧是由构造方法或实例方法创建的,那么该对象引用this将会存放在index为0的slot处,其余的参数按照参数表顺序继续排列。
    • Slot是可以重复利用的。 【细节】
      在这里插入图片描述
    • 这个方法的局部变量表长度为3,c复用了已经销毁了的b的slot。
  • 局部变量表中的变量也是重要的垃圾回收根节点,只要被局部变量表中直接或间接引用的对象都不会被回收。

(2)操作数栈:

  • 每一个独立的栈帧都还包括一个后进先出的操作数栈,也可以称之为表达式栈

  • 在方法执行过程中,根据字节码指令,往操作数栈中写入数据或提取数据【入栈,出栈】。

  • 操作数栈主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储的空间。

  • 操作数栈就是JVM执行引擎的一个工作区,当一个方法刚开始执行的时候,一个新的栈帧也会被创建出来。

  • 每一个操作数栈都会拥有一个明确的栈深度用于存储权值,其所需的最大深度在编译器就确定了,保存在Code属性中。【max_stack

  • 32位的数据类型占一个栈单位的深度。

  • 64位的数据类型占两个栈单位的深度。

  • 栈顶缓存技术:

    • 将栈顶元素全部缓存在物理CPU的寄存器中,以此降低对内存的读/写次数,提升执行引擎的执行效率。

(3)动态链接:

  • 每一个栈帧内部都包含一个指向运行时常量池中该栈所属方法的引用。包含这个引用的目的是为了支持当前方法的代码能够实现动态链接。
  • 在Java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用保存在class文件的常量池里面。动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用。

(4)方法返回地址:

  • 正常返回的情况:当执行引擎遇到任意一个方法返回的字节码指令,就会有返回值传递给上层的方法调用。

  • 存在异常的返回情况:当出现未处理耳朵异常时,就会非正常的退出,通过异常退出的,返回地址要通过异常表来确定,栈帧中一般不会保存这部分信息,并且不会返回任何值给上一层。

(5)栈帧的附加信息

  • 栈帧中还会存在一些其他信息,比如对调试支持的信息。

本文章是《JVM从零到一系列》的一篇,这个系列将会持续更新。 此系列文章所有内容来自于作者的整理,主要参考以下方面:

  • 《深入理解Java虚拟机》
  • 宋红康老师系列课程
  • 大厂面试题
  • JVM8官方参考文档
  • JVM8源代码

本文所有图均为自己绘制,转载请附上作者署名和本文链接,谢谢啦^^~~ JVM相关的资料确实不是很多,希望我的总结能给大家一些帮助!
谢谢您的观看!
作者署名 – ATFWUS

猜你喜欢

转载自blog.csdn.net/ATFWUS/article/details/106658161