JVM理论(三)运行时数据区--PC寄存器/虚拟机栈/本地方法栈

运行时数据区(JVM内存结构)

JVM内存结构

内存是非常重要的资源,是硬盘和CPU的中间桥梁,承载操作系统和应用程序的实时运行.JVM内存布局规定java在运行过程中内存申请、分配、管理的策略,保证JVM高效稳定运行。不同的JVM对于内存划分和管理机制存在部分差异(如J9和JRocket没有方法区,而Hotspot存在) 。

运行时数据区包括堆、方法区、PC寄存器(即程序计数器)、虚拟机栈、本地方法栈。

其中黄色的为多个线程共享,绿色的为单独线程私有的,即:

  • 线程私有:PC寄存器(即程序计数器)、虚拟机栈、本地方法栈
  • 线程共享:堆、方法区(永久区)

 

PC寄存器(程序计数器

程序计数器作用示意图

概述

JVM中程序计数寄存器用来存储下一条将要执行指令的地址(当前线程所执行的字节码的行号指示器),执行引擎从PC寄存器获取到指令地址后进行执行对应的指令。

PC寄存器特点:

  • 内存空间很小几乎忽略不计
  • 运行速度最快的内存区域
  • 线程私有,与线程生命周期一致
  • 存储当前线程正在执行的java方法的指令地址
  • 不会出现OOM,无GC

常见问题

为什么要用PC寄存器记录当前线程的执行地址?使用它存储字节码指令地址有什么用?

因为CPU需要不停的切换各个线程,此时切换回来后,就得知道接着从哪里开始继续执行。

JVM的字节码解释器需要通过PC寄存器的值来明确下一条应该执行什么样的字节码指令。

PC寄存器为什么被设定为线程私有的?

程序通常运行在多线程环境下,CPU会不停的做任务切换,会导致程序经常中断和恢复,为了保证程序运行结果分毫不差,所以每个线程都有独立的PC寄存器,分别独立的记录各个线程正在执行的当前字节码指令地址,这样各个线程之间便可以独立计算,从而不会出现相互干扰,保证程序运行结果正确。

虚拟机栈 

虚拟机栈内部组成

概述

由于跨平台设计,Java的指令都是根据栈来设计的,不同平台的CPU架构不同,所以不能设计为基于寄存器的。

栈是程序运行时基本单位(即解决程序如何运行、处理数据),堆是存储单位(即数据如何存储,存储在哪里)。

虚拟机栈特点:

  • 跨平台
  • 指令集小,编译器容易实现
  • 性能相比寄存器下降(因为实现相同功能需要更多的指令)
  • LIFO/FILO(后进先出/先进后出)
  • 生命周期和线程周期保持一致
  • JVM对栈操作只有两个:方法执行(入栈/压栈)和方法结束(出栈)
  • 会出现OOM,无GC(因为只有进出栈)
  • 可以通过-Xss设置栈内存大小

栈运行原理

  • 不同线程中的栈帧是不允许存在相互引用的,即不可能在一个栈帧中引用另外一个线程的栈帧;
  • 如果当前方法调用了其他方法,在方法返回时候,当前栈帧会将此方法的结果给到前一个栈帧,随后JVM会丢弃当前栈帧,使得前一个栈帧重新成为当前栈帧;
  • 在java运行过程中正常的函数返回(return)和抛出异常都会导致栈帧被弹出.

栈帧

一个虚拟机栈内部保存多个栈帧,栈帧是虚拟机栈的基本组成单位,且每个栈帧对应java程序中的一个方法。栈帧是一个内存区块,即:

  • 局部变量表(局部变量数组/本地变量表)
  • 操作数栈(表达式栈)
  • 动态链接(指向运行时常量池的方法引用)
  • 方法返回地址(方法正常退出或者异常退出的定义)
  • 其他附加信息

注意:有的资料中也将动态链接、方法返回地址、其他附加信息统一称为栈数据区

局部变量表

  • 被定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量。这些数据类型包括基本数据类型,对象引用以及方法返回地址类型
  • 局部变量表所需的容量大小是在编译期确定下来的,并保存在方法的code属性的maximum local varaiables数据项中,在方法运行期间是不会改变局部变量表大小
  • 属于线程私有数据,不存在数据安全问题
  • 局部变量表中的变量只在当前方法调用中有效,方法调用结束后,随着方法栈帧销毁,局部变量表也随之销毁
  • 局部变量表的存储基本单元被称为Slot(变量槽),32位的数据类型只占用一个slot,64位的类型占用两个slot
  • 若当前帧由构造方法或者实例方法创建,那么该对象引用this将会存放在index为0的slot中
  • 局部变量表中的slot是可以重用的,从而节省资源
  • 局部变量必须显示赋值,否则编译不通过,而成员变量存在默认赋值,可不用显示赋值
  • 局部变量表中的变量也是重要的垃圾回收根节点,主要被局部变量表直接或间接引用的对象都不会被回收

操作数栈(表达式栈)

操作数栈的作用就是在方法执行过程中,根据字节码指令,在栈中写入数据或者提取数据,即入栈(push)或者出栈(pop),比如执行求和,复制,交换等操作时。它的特点包括:

  • 保存计算过程的中间结果,同时作为计算过程中变量的临时存储空间
  • 操作数栈也会拥有一个栈深度用于存储数值。该深度也在编译期就定义好了,保存在方法的Code属性的max stack的值
  • 操作数栈并非采用访问索引的方式来进行数据访问,而只能通过标准的入栈和出栈操作来完成一次数据访问
  • 被调用方法有返回值时,该值也会被压入当前栈帧的操作数栈中

动态链接(指向运行时常量池的方法引用)

每个栈帧内部都包括一个指向运行时常量池中该栈帧所属方法的引用,该引用目的就是为了支持当前方法代码能够实现动态链接。

在Java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用保存在class文件的常量池里,而动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用

扩展

方法绑定机制:早期绑定和晚期绑定

静态链接:当一个字节码文件被装载进JVM时,如果被调用的目标方法在编译期可知,且运行期保持不变,这种情况下降调用方法的符号引用转换为直接引用的过程称之为静态链接

动态链接:如果被调用的目标方法在编译期无法被确定,只能通过运行期间将符号引用转换为直接引用,称为动态链接

绑定是一个字段、方法或者类在符号引用被替换为直接引用的过程,仅发生一次。而早期绑定可以对应静态链接,晚期绑定对应动态链接。

方法返回地址

方法返回地址就是用来存放调用该方法的pc寄存器的值。方法结束有正常退出和异常退出:

  • 正常退出:调用者的pc寄存器的值作为返回地址,即调用该方法的指令的下一条指令地址
  • 异常退出:返回地址通过异常表来确定,栈帧中一般不会保存这部分信息,且不会给他的上层调用者产生任何返回值

本地方法栈

本地方法: 一个Native方法就是一个Java调用非Java代码的接口,它的初衷就是融合C/C++程序;使用本地方法,我们可以用Java实现了Jre与底层系统的交互。

Java虚拟机用于管理Java方法的调用,而本地方法栈用于管理本地方法的调用。本地方法栈也是私有的。和Java虚拟机特点基本相同。

常见问题

开发中JVM的栈遇到的异常

首先Java的虚拟机规范允许Java的栈大小是动态的或者固定不变的。

a.当采用固定大小时,若线程请求分配的栈容量超过虚拟机栈允许的最大容器容量,则JVM会抛出StackOverFlowError异常

b.当采取动态扩展时,当尝试扩展时候无法申请到足够内存或者没有足够内存创建对应的虚拟机栈,则会抛出OutOfMemoryError异常

调整栈大小,保证不出现栈溢出吗?

不能,只能延迟栈溢出的时间

分配的栈内存越大越好吗?

不是,它避免不了栈溢出等异常,而且会占用其他内存空间

垃圾回收是否会涉及到虚拟机栈?

不会,它只存在栈溢出或者OOM,因为栈只涉及到入栈和出栈,但不会GC

方法中定义的局部变量是否线程安全?

得具体问题具体分析,

如果只有一个线程可以操作该变量,则线程安全;

如果多个线程可以操作该变量,则线程安全;比如通过形参传入一个stringbuilder非安全对象,若其他线程再操作stringbuilder对象时,则可能会改变结果,造成线程不安全;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/20667.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[数据存储]HDFS的简介、初始化配置与运行

文章目录 HDFS简介HDFS数据存储访问方式HDFS节点HDFS的数据存储原理HDFS元数据(Block块管理)HDFS的数据读写流程HDFS数据写入流程HDFS数据读取流程 启动HDFSHDFS初始化配置${HADOOP_CONF_DIR}/hdfs-site.xml文件系统格式化启动HDFS查看启动状态 HDFS简介…

又是一条慢 SQL 改写,拿捏!

作者分享了一条慢 SQL 分析和优化的过程,总结出切实有效的优化手段。 作者:马文斌 MySQL 爱好者。 本文来源:原创投稿 爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。 背景 开发同学丢…

※Redis的事务、乐观锁和悲观锁

1.是神魔 在高并发的环境下,多个线程去竞争同一个资源, 比较常见的有高铁抢票系统,商品秒杀系统等,我们需要保证数据正确,同时系统的吞吐也要尽可能高。2.解决方案 1. 一般多线程同步我们就会想到加锁,用…

数据库作业——select查询操作

数据库作业 创建数据库 mysql> create table worker( -> 部门号 int(11) not null, -> 职工号 int(11) primary key not null,-> 工作时间 date not null,-> 工资…

MiniGPT4 在RTX-3090 Ubuntu服务器部署步骤详解

主要参考知乎帖子: MiniGPT-4 本地部署 RTX 3090 - 知乎 MiniGPT-4部署比麻烦,首先需要获取LLaMA权重,并结合Vicuna的bitwise XOR增量文件完成Vicuna模型权重生成,最后准备好预训练的MiniGPT-4进行模型部署。为了便于理解&#…

这3个方法教你录音转文字怎么导出来

在日常生活中,我们有时候可能需要将音频转换为文本,因为这样可以节省我们听的时间,还能让记录更加清楚明了。那么,你是否知道录音转文字怎么导出来?如果你不懂,请接着看我下面介绍的三种方法吧!…

pytorch安装问题【超级简单版】

pytorch安装问题 当前遇到的问题: python3.9无法安装读取coco数据集的 pycocotools-windows,那么需要切换版本到3.6/7/8,但是切换到python 3.6之后,无法安装torchvision和pytorch【在python就叫torch】,显示没有这个版本 pip i…

[RocketMQ] Broker 消息重放服务源码解析 (十三)

构建消息文件ConsumeQueue和IndexFile。 ConsumeQueue: 看作是CommitLog的消息偏移量索引文件, 存储了它所属Topic的消息在Commit Log中的偏移量。消费者拉取消息的时候, 可以从Consume Queue中快速的根据偏移量定位消息在Commit Log中的位置。IndexFile索引文件: 看作是Commi…

UE4 像素流的一些使用技巧

一、测试像素流的三种方法,前提是熟悉官网像素流送那套流程,这里只是讲如何不用打包就能测试的方法 1.第一种方法是vs安装unrealvs扩展,因为安装这个拓展后加可以加命令行参数启动项目https://docs.unrealengine.com/4.26/zh-CN/ProductionP…

怎么用PDF24 Tools工具在线进行PDF文件合并

PDF文件是经常会被用到,它在我们的日常生活和工作中扮演着重要的角色。PDF文件合并是将多个PDF文件合并为单个文件,这个过程通常是为了方便管理多个PDF文件,或者将多个PDF文件合并为一个整体以便于共享或打印。既然如此,如何快速合…

Spring5学习笔记--详细一文通

Spring5学习笔记--详细一文通 1 Spring 框架概述1.1 Spring 5 简述1.2 Spring5入门案例1.2.1 Spring5下载1.1.2 打开 idea 工具,创建普通 Java 工程1.2.3 导入 Spring5 相关 jar 包1.2.4 创建普通类,在这个类创建普通方法1.2.5 创建 Spring 配置文件&…

迅捷录屏软件使用中的注意事项

取消勾选时就不会出现右侧的悬浮框滑动的窗口了。 取消鼠标高亮后,录制的视频就不会出现一个空心的小圆圈。