【基础篇】九、程序计数器 JVM栈

news2024/11/14 13:34:05

文章目录

  • 0、运行时数据区域
  • 1、程序计数器
  • 2、JVM栈
  • 3、JVM栈--栈帧--局部变量表
  • 4、JVM栈--栈帧--操作数栈
  • 5、JVM栈--栈帧--桢数据
  • 6、栈溢出
  • 7、设置栈空间大小
  • 8、本地方法栈

0、运行时数据区域

在这里插入图片描述

JVM结构里,类加载器下来,到了运行时数据区域,即Java程序运行时,JVM管理的内存区域,其又分为:

在这里插入图片描述

栈这里可以细划分为两部分:

  • Java虚拟机栈:保存在Java中的方法
  • 本地方法栈:保存的native方法,c++实现的那些

1、程序计数器

程序计数器(Program Counter Register)也叫PC寄存器,每个线程会通过它来记录接下来要执行的的字节码指令的地址

在这里插入图片描述

举个例子:

在这里插入图片描述

类加载阶段,类加载器将字节码指令读到内存中后,将原来的偏移量改为内存地址,每条字节码指令都对应一个内存地址

在这里插入图片描述

执行完当前这一条指令后,JVM的执行引擎(JIT、解释器等)根据程序计数器中记录的数据,就拿到了接下来要执行的指令的地址。

在这里插入图片描述

多线程下,CPU从线程A切换到线程B,得知道线程B之前解释执行到哪一句指令了,此时JVM的程序计数器就发挥作用了

在这里插入图片描述

2、JVM栈

JVM的栈采用栈的数据结构,保存方法调用的基本数据,先进后出,其中,每一个调用的方法用一个叫栈帧的东西存。

如下图,流程为:

main入栈
study入栈
eat入栈
eat出栈.....
sleep入栈
sleep出栈.....
study出栈.....
main出栈.....
执行结束!

在这里插入图片描述

断点打在C方法里,看下IDEA中debug的栈信息,点击栈里的每个方法,跳到当前该方法执行在的那一行:

在这里插入图片描述

修改程序,让C程序抛出异常:

public class FrameDemo{
	
	public static void main(String[] args){
		A();
	}
	public static void A(){
		System.out.println("A执行了...");
		B();
	}
	public static void B(){
		System.out.println("B执行了...");
		C();
	}
	public static void C(){
		System.out.println("C执行了...");
		throw new RuntimeException("测试");   //异常
	}
}

可以发现,结果里也展示了异常发生时的栈信息,以及每个方法具体执行到哪一行了:

在这里插入图片描述

JVM的栈随着线程的创建而创建,也随着线程的销毁而回收,每个线程都有一个自己的JVM栈

在这里插入图片描述

3、JVM栈–栈帧–局部变量表

栈里的一个个栈帧,由三部分构成:

在这里插入图片描述

局部变量表用来存方法执行过程中的所有局部变量,类被编译成字节码时,局部变量表的内容就确定了:

在这里插入图片描述

想访问一个变量,自然是要在它声明定义之后,所以局部变量表里的起始PC就是保存了从哪一行字节码指令开始,可以访问这个变量,对于变量i,其值就为2。而长度字段为3,即代表在2.3.4这三行里可以访问i。总之就是通过局部变量表控制可以访问每个变量的范围。 是字节码文件中的局部变量表,它的作用是做安全性上的校验,比如变量的生效范围。

在这里插入图片描述

而栈帧自己的局部变量表就是一个数组,数组的每个位置叫槽slot,long和double类型占用两个槽,其他类型占用一个槽。 如上图,i占0号位,一个槽,后面long类型的j则占两个。再看字节码文件里局部变量表中的序号,其实就是这个局部变量i,在栈帧的局部变量表的起始槽的编号。

在这里插入图片描述

实例方法中的序号为0的位置存放的是this,指的是当前调用方法的对象,运行时会在内存中存放实例对象
的地址。

在这里插入图片描述

局部变量表也会存方法的形参,且顺序与形参顺序一致。

一句话,局部变量表中存的是实例方法的this对象、方法的形参、方法体中声明的局部变量

注意,局部变量表的槽可以重复使用,一旦某个局部变量不再生效,当前槽就可以再次被使用

public void test4(int k,int m){
	
	{
		int a = 1;
		int b = 2;
	}
	{
		int c = 1;
	}
	int i = 0;
	long j = 1;
}

执行完b=2时,栈帧的局部变量表长这样:

在这里插入图片描述

再往下走,字节码为:

iconst_1
istore_3

即把字面量1放入了3号槽,复用了a变量的槽。同理往下推,最终的局部变量表长这样:

在这里插入图片描述

因此,上面的代码在其栈帧的局部变量表中会占用6个槽,而不是简单的1+1+1 +1+1 +1 +1+2 = 9

4、JVM栈–栈帧–操作数栈

  • 操作数栈是JVM在执行指令时,用来存放中间数据的区域
  • 栈的数据结构,压栈+弹栈
  • 编译器就可决定操作数栈的最大深度,用jclasslib验证:
    在这里插入图片描述

分析下以上源码的字节码:

在这里插入图片描述

操作数栈的变化过程如下:

在这里插入图片描述
在这里插入图片描述

最终状态:
在这里插入图片描述

可以看到,整个过程中,操作数栈里最多有两个数据,即操作数栈的最大深度为2,此时创建栈帧时,创建深度为2的操作数栈即可。

5、JVM栈–栈帧–桢数据

桢数据主要含:

  • 动态链接
  • 方法出口
  • 异常表引用
Part1:动态链接

在这里插入图片描述
当前类的字节码中引用了别的类的方法或属性,要将符号引用(#10)转成内存地址。动态链接就保存了编号到运行时常量池的内存地址的映射关系。

Part2:方法出口

在栈帧的桢数据中,存放了上一个方法的地址(sleep方法的桢数据有study方法执行到哪一行的信息)
在这里插入图片描述

当方法正常结束或发生异常结束时,当前栈帧被弹出,同时程序计数器指向上一个栈帧的下一条指令地址:

在这里插入图片描述

Part3:异常表引用

存放异常捕获的范围,以及这个范围发生异常后跳哪一行。 eg:看异常表的第一行,从起始PC 0到结束PC 2,如果发生异常,就跳转到第7行,astore_0即把捕获的异常对象e放到局部变量表中,因为catch块中大概率会用到e对象

在这里插入图片描述

6、栈溢出

一个线程的栈里,栈帧过多,占用的内存到达了分配的最大值,再入栈就会StackOverflowError,即栈溢出。

在这里插入图片描述

如下为OpenJDK8里JVM的源码,不指定栈的大小时,JVM创建的是一个默认大小的栈,不同的操作系统里,这个默认值也不同。

在这里插入图片描述

用无停止条件的递归来看栈溢出:

public static int count = 0;
//递归方法调用自己
 public static void recursion(){
 
	 System.out.println(++count);
	 
	 recursion();
	 
 }

在这里插入图片描述
运行,默认情况下,main线程的栈里放了大约10473个栈帧:

在这里插入图片描述

7、设置栈空间大小

添加JVM参数:

-Xss栈大小

单位默认是byte字节(默认,必须是 1024 的倍数),可选k或者K(KB)、m或者M(MB)、g或者G(GB)

eg:
-Xss1048576 
-Xss1024K 
-Xss1m
-Xss1g

在这里插入图片描述
设置线程栈空间大小,还可以用另一个参数:

//注意等号
 -XX:ThreadStackSize=1024

HotSpot对栈空间的大小有范围限制,Windows(64位)下的JDK8测试最小值为180k,最大值为1024m,超过这个范围,即使设置了也不会生效。

//无效
-Xss1k
-Xss1025m

当然,局部变量过多(栈帧局部变量表大)、操作数栈深度大,在相同大小下,能存的栈帧数量也就少了。最后,工作中,该值建议用

-Xss256k

8、本地方法栈

JVM栈中存的是Java方法的栈桢,本地方法栈里存的则是native本地方法的栈帧。不过在HotSpot虚拟机中,JVM栈和本地方法栈实现上使用了同一个栈空间。

在这里插入图片描述

如下:

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Navicat for Mysql怎么执行创建表的脚本

Navicat for Mysql怎么执行创建表的脚本 Navicat 怎么执行sql文件 Navicat 执行创建表语句 Navicat 执行sql语句 Navicat 怎么创建表语句 1、打开Navicat数据库管理工具; 2、点击菜单栏上的“工具”,选择“命令列界面”; 打开了命令列界面…

Vue学习计划-Vue3--核心语法(三)computed计算属性、watch监听、watchEffect函数

1. computed计算属性 作用:根据已有数据计算出新数据(和Vue2中的computed作用一致)。 2. watch监视与watchEffect 1. watch 作用:监视数据的变化(和Vue2的watch作用一致)特点:Vue3中的watch…

Flume基础知识(五):Flume实战之实时监控目录下多个新文件

1)案例需求: 使用 Flume 监听整个目录的文件,并上传至 HDFS 2)需求分析: 3)实现步骤: (1)创建配置文件 flume-dir-hdfs.conf 创建一个文件 vim flume-dir-hdfs.conf …

一起学docker(六)| Dockerfile自定义镜像 + 微服务模块实战

DockerFile 是什么 Dockerfile是用来构建Docker镜像的文本文件,是由一条条构建镜像所需的指令和参数构成的脚本。 构建步骤 编写Dockerfile文件docker build命令构建镜像docker run运行镜像 Dockerfile构建过程 基础知识 每个保留字指令都必须为大写字母且后面…

逻辑回归(LR)----机器学习

基本原理 逻辑回归(Logistic Regression,LR)也称为"对数几率回归",又称为"逻辑斯谛"回归。 logistic回归又称logistic 回归分析 ,是一种广义的线性回归分析模型,常用于数据挖掘&#…

『年度总结』逐梦编程之始:我的2023学习回顾与展望

目录 这篇博客,我将回顾2023年编程之旅的起点,同时展望2024年的新征程。 前言 我与Python 我与C语言 第一篇正式博客: 第二篇正式博客(扫雷): 指针学习笔记: C语言学习笔记: 我与数据结构…

SCT52A40——120V,4A,高频高压侧和低压侧栅极驱动器,替代UCC27200/UCC27201/MIC4604YM等

• 8-24V宽供电电压 • 驱动高侧和低侧N通道MOSFET • 4A峰值输出源电流和汇电流 • 升压电源电压范围可达120V • 集成阴极负载二极管 • TTL兼容输入,-10V输入 • 45ns传输延迟 • 1000pF负载下7ns上升和4.5ns下降时间 • 2ns延迟匹配时间 • 静态电流252uA • 15…

JDK、JRE、JVM的联系与区别

JDK、JRE、JVM的联系与区别 一、JDK,JRE,JVM定义 JDK(Java Development Kit),包含JRE,以及增加编译器和调试器等用于程序开发的文件。 JRE(Java Runtime Environment),包含Java虚拟机、库函数、运行Java应用程序所必须的文件。 JVM(Java Virtual Machine)是一个虚…

Vue中的选项式 API 和组合式 API,两者有什么区别

Vue中的选项式 API(Option API)和组合式 API(Composition API)是两种不同的组件编写方式,它们各有特点和适用场景: 选项式 API(Option API): 传统方法:Vue最初的编程范式…

LeetCode 热题 100——42. 接雨水

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。 示例 1: 输入:height [0,1,0,2,1,0,1,3,2,1,2,1] 输出:6 解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表…

一起玩儿物联网人工智能小车(ESP32)——25. 利用超声波传感器测量距离

摘要:本文介绍如何利用超声波传感器测量障碍物的距离 测量距离是智能小车经常要用到的功能,今天就来介绍一个最常用的测量距离的传感器——超声波传感器。 超声波传感器的测距原理是利用超声波发射器向某个方向发射超声波,与此同时&#xff…

Health System Pro - Plug Play Solution

Health System为您提供了可重复使用的健康组件、健康条和碰撞块组件,可以轻松定制和扩展,以满足任何项目的需求。通过使用Health System,您可以节省时间和精力,避免为每个项目或游戏实体重写健康逻辑,从而带来更高效的…

计算机网络(9):无线网络

无线局域网 WLAN 无线局域网常简写为 WLAN (Wireless Local Area Network)。 无线局域网的组成 无线局域网可分为两大类。第一类是有固定基础设施的,第二类是无固定基础设施的。所谓“固定基础设施”是指预先建立起来的、能够覆盖一定地理范围的一批固定基站。 …

Python----matplotlib库

目录 plt库的字体: plt的操作绘图函数: plt.figure(figsizeNone, facecolorNone): plt.subplot(nrows, ncols, plot_number): plt.axes(rect): plt.subplots_adjust(): plt的读取和显示相关函数: plt库的基础图…

Day22 二叉树part08 235.二叉搜索树的最近公共祖先 701.二叉搜索树中的插入操作 450.删除二叉搜索树中的节点

二叉树part08 235.二叉搜索树的最近公共祖先 701.二叉搜索树中的插入操作 450.删除二叉搜索树中的节点 235. 二叉搜索树的最近公共祖先 方法一:递归法(利用二叉搜索树性质) class Solution { public:TreeNode* lowestCommonAncestor(TreeN…

74HC595驱动数码管程序

数码管的驱动分静态扫描和动态扫描两种,使用最多的是动态扫描,优点是使用较少的MCU的IO口就能驱动较多位数的数码管。数码管动态扫描驱动电路很多,其中最常见的是74HC164驱动数码管,这种电路一般用三极管作位选信号,用…

管理组件状态

概述 在应用中,界面通常都是动态的。如图1所示,在子目标列表中,当用户点击目标一,目标一会呈现展开状态,再次点击目标一,目标一呈现收起状态。界面会根据不同的状态展示不一样的效果。 图1 展开/收起目标…

50、实战 - 利用 conv + bn + relu + add 写一个残差结构

上一节介绍了残差结构,还不清楚的同学可以返回上一节继续阅读。 到了这里,一个残差结构需要的算法基本都介绍完了,至少在 Resnet 这种神经网络中的残差结构是这样的。 本节我们做一个实战,基于之前几节中手写的 conv / bn 算法&…

python封装接口自动化测试套件 !

在Python中,我们可以使用requests库来实现接口自动化测试,并使用unittest或pytest等测试框架来组织和运行测试套件。以下是一个基本的接口自动化测试套件封装示例: 首先,我们需要安装所需的库: pip install requests …

ssm基于web的志愿者管理系统的设计与实现+vue论文

摘 要 使用旧方法对志愿者管理系统的信息进行系统化管理已经不再让人们信赖了,把现在的网络信息技术运用在志愿者管理系统的管理上面可以解决许多信息管理上面的难题,比如处理数据时间很长,数据存在错误不能及时纠正等问题。这次开发的志愿者…