JVM 运行时数据区域

目录

前言

程序计数器

 java虚拟机栈

本地方法栈

java堆

 方法区

运行时常量池


前言

        首先, java程序在被加载在内存中运行的时候, 会把他自己管理的内存划分为若干个不同的数据区域, 就比如你是一个你是一个快递员, 一堆快递过来需要你分拣, 这个时候, 你就需要根据投放的目的地来为其投递到不同的流水线上, 以方便下一步操作

        JVM同样也是如此:  java咋运行的时候, 会把class加载到内存中, 并将其解析成为java运行时的数据结构. 

其中

  • 所有线程共享的区域: 
    • 方法区
  • 线程隔离的数据库
    • 虚拟机栈
    • 本地方法栈
    • 程序计数器

下面我们逐步来拆解各个区域的功能


程序计数器

        程序计数器的内存区域是个非常小的区域, 它用来表示当前的线程所执行到的指令的位置, 方便线程切换之后恢复之前的运行状态.   

        在Java的虚拟机概念模型中指出, 通过字节码解释器来修改程序计数器中值, 来选取下一条需要执行的字节码指令.

        程序计数器是程序控制的指示器, 里面很多内容, 例如分支, 循环, 异常处理, 线程恢复等都是依赖程序计数器完成.

        java虚拟机的多线程是通过线程轮流切换, 分配处理器执行时间 的方式来实现的, 在任何一个确定的时刻, 一个处理器(多核处理器里面就是内核)只会执行一个线程中的一条指令, 因此一个线程切换成另外一个线程之后, 如果不保存别切换出处理器的线程的运行状态, 那么下次这个线程重新上处理器执行的时候, 就不知道上次运行到什么地方了, 就需重新运行. 

        因此按道理来说, 每一个线程都应该有自己的运行状态, 也就是每个线程应该有自己的程序计数器(内存中), 因此我们将这种类型的内存区域称之为, 线程私有内存        


 java虚拟机栈

        同上述的程序计数器一样, 虚拟机栈也是线程私有的? 为什么是线程私有, 就应该先了解什么是虚拟机栈,  功能是什么

        既然是线程私有的, 也应该同程序计数器一样, 他们的生命周期与线程的生命周期一样 , 与程序计数器不同的是, 虚拟机栈描述的是, java方法执行的线程内存模型: 

        每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程.


解释: 

  • 局部变量表: 存放了 编译期间可知的各种java虚拟机基本数据类型(boolean、byte、char、short、int、float、long、double), 对象引用, 例如String, (reference类型,它并不等同于对象本身,可能是一个指向对象起始地址的引用指针), 和returnAddress类型(返回值)
  • 局部变量所需的内存在编译期间完成分配, 进入一个方法时候, 这个方法需要在栈帧中分配多大的局部变量空间是完全确定的, 在运行期间是不会更改局部变量表的大小(变量槽*)

 java虚拟机里面什么是变量槽?  

  • 在 Java 虚拟机(JVM)中,局部变量槽(local variable slots)是管理方法中局部变量和方法参数的关键机制。它们在方法调用时提供存储空间,用于存储不同类型的数据
  • 每一个java方法都有一个局部变量表, 用于存放该方法中的局部变量, 方法参数和一些中间计算结果, 局部变量槽就是这个表中的单元, 每一个槽可以存放一个或者多个数据项, 取决于jVM实现的细节

局部变量槽的结构

  • 局部变量表是一个数组, 每一个槽都可以存储一个数据项, 槽的数量和类型由方法的局部变量表的大小来决定, 这个大小在编译期间确定, 由jvm维护
  • 基本数据类型: byte、short、int、char、float 和 boolean 类型通常占用一个槽(4 字节), long 和 double 类型占用两个槽(8 字节),因为它们的大小比单个槽大
  • 对象引用: 对象引用(Object 类型的变量)通常占用一个槽(4 字节或 8 字节,取决于 JVM 实现)

类似于结构体的对齐

  • long 和 double 类型的数据需要按 8 字节对齐,因此在局部变量表中,它们占用两个槽,并且后续的槽也会对齐,以避免对齐问题

使用

  • 当方法被调用时,JVM 会创建一个新的栈帧,其中包括一个局部变量表, 这张表的大小由方法的 code 属性定义,并在方法调用时分配
  • 在方法执行期间,局部变量槽用于存储方法参数和局部变量 , 字节码指令通过对这些槽的读写操作来执行计算
  • 当方法执行完毕后,其栈帧会被弹出,局部变量槽的数据会被清除。栈帧的销毁会导致局部变量槽的空间释放

局部变量槽的示例

public void exampleMethod(int a, long b, double c) {int x = 10;double y = 20.5;
}

在这个方法中,局部变量槽的分配可能如下:

  • 槽 0:存储参数 a(int 类型,占用 1 个槽)。
  • 槽 1 和 2:存储参数 b(long 类型,占用 2 个槽)。
  • 槽 3 和 4:存储参数 c(double 类型,占用 2 个槽)。
  • 槽 5:存储局部变量 x(int 类型,占用 1 个槽)。
  • 槽 6 和 7:存储局部变量 y(double 类型,占用 2 个槽)。

意义

  • 字节码指令操作局部变量槽
    • iloadistore:加载和存储 int 类型数据。
    • lloadlstore:加载和存储 long 类型数据。
    • dloaddstore:加载和存储 double 类型数据。
  • 这些指令使用局部变量槽的索引来指定操作的数据项
  • !!!局部变量槽属于栈帧的一部分,而栈帧则是 Java 虚拟机栈的一个组成部分

 

        太抽象了听不懂? 接下来我使用main方法列举一个例子: 

  • 当你启动一个java程序的时候, JVM首先会启动一个main线程, 也称为主线程, 主线程负责执行main方法, main方法是程序的入口点, JVM从main方法开始执行代码
  • 在执行main方法之前, JVM会为main方法创建一个栈帧, 这个栈帧会压入到JVM的栈中去, 也叫作虚拟机栈, 每一个线程都拥有自己独立的虚拟机栈(main方法的栈帧由main线程所有)
  • main方法局部变量表会被初始化, 存储 main 方法的参数(通常是一个字符串数组 String[] args)。操作数栈被初始化为空
  • main 方法的字节码被逐条执行。每条指令都会操作局部变量表和操作数栈。例如,加载局部变量到操作数栈,执行加法运算,然后将结果存储回局部变量表
  • 如果 main 方法调用了其他方法,每调用一个方法,JVM 都会创建一个新的栈帧,并将其压入虚拟机栈中。
  • 当 main 方法执行完毕,它的栈帧会被弹出虚拟机栈
  • 如果 main 方法没有显式地返回值,它将返回 void,JVM 会清理 main 方法的栈帧,并可能终止程序的执行(如果这是程序的最后一个方法)

什么是栈深度? 

        指当前线程的虚拟机栈中栈帧的总数。栈深度从栈底到栈顶表示方法调用的嵌套层次, 例如main方法中调用其他方法, main线程就会给自己管理的java虚拟机栈中再创建一个栈帧, 栈深度增加 1, 

        但是栈的深度也不是能随意增加的 , 有一个上限, 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常(栈溢出)

        但是有的java虚拟机的实现, 是可以动态扩容栈容量的, 此时如果动态扩展的时候, 没有足够的内存 , 就会抛出OutOfMemoryError 异常(内存溢出)


本地方法栈

        本地方法栈和java虚拟机栈所发挥的作用是非常相似的, 区别在于, java虚拟机栈是为java方法服务的, 本地方法栈是只是为使用到的本地方法服务的. 

        这也就是为什么大家一开始学习java虚拟机的时候, 虽然学习了本地方法栈和java虚拟机栈之后还是很容易忘记, 是因为你没有了解其本质, 一个是为java方法服务, 所以叫java虚拟机栈, 一个为本地方法服务, 因此叫做本地方法栈.  (其实我觉得把java虚拟机栈改名为java虚拟机方法栈, 或者java方法栈可能会更见名知意)

        因为他两其实作用一样, 有的虚拟机甚至直接将java虚拟机栈和本地方法栈合二为一, 与虚拟机栈一样,本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出StackOverflowErrorOutOfMemoryError异常

        这里就不过多解释 ... ...



java堆

        算的上是java运行时内存区域的老大了 ... 

        java堆区是 虚拟机管理的最大一块内存之一, java堆是被所有的线程共享的一块区域, 在java虚拟机启动时就被创建 , 此区域唯一的目的就是存放对象实例, java世界里面几乎所有的对象都在这里分配了内存. 

        这里几乎这个字眼, 说明后续的java虚拟机版本中, 可能存在不在堆中创建存储的对象

        java堆也是垃圾收集器管理的内存区域, 因此被称为GC 堆, GC全称Garbage Collected Heap, 翻译就是垃圾收集堆. 

        既然涉及到对象的回收, 那么就需要考虑几个问题(后面解答)

  1. 这里的回收是什么意思? 
  2. 为什么要回收? 
  3. 不同的对象, 回收的标准一样吗?  怎么对一个对象进行回收? 
  4. 回收的结果是什么? 

        其实你可以猜想, 既然是回收, 如果在生活中, 你觉得什么东西是有必要回收的, 无非就是这几种:

  • 过时的
  • 很少用到的, 而且很占空间
  • 没有价值的
  • 不常用的

        有一些java虚拟机的实现中, 就是基于常用性来进行回收的, 例如分代回收机制. 所以Java堆中经常会出现, 新生代, 老年代, 永久代, Eden空间, From Survivor空间, To Survivor空间等名词 (这些区域划分仅仅是一部分垃圾收集器的共同特性或者说设计风格而已,而非某个Java虚拟机具体实现的固有内存布局)

        但是到了今天,垃圾收集器技术与十年前已不可同日而语,HotSpot里面也出现了不采用分代设计的新垃圾收集器,再按照上面的提法就有很多需要商榷的地方了. 但是无论是什么回收技术, 目的只是为了更好地回收内存,或者更快地分配内存

        Java堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的,这点就像我们用磁盘空间去存储文件一样,并不要求每个文件都连续存放, 但对于大对象(典型的如数组对象),多数虚拟机实现出于实现简单、存储高效的考虑,很可能会要求连续的内存空间

         Java堆既可以被实现成固定大小的,也可以是可扩展的,不过当前主流的Java虚拟机都是按照可扩展来实现的


怕你们忘记, 再看看这个图: 


 方法区

        方法区同堆区都是线程共享的, 它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据. 

        在JDK1,8以前, 许多程序员, 都喜欢把方法区称之为 永久代, 但事实上, 本质上这两者并不是等价的,因为仅仅是当时的HotSpot虚拟机设计团队选择使用永久代来实现方法区而已.  这样就可以像管理堆内存那样, 省去专门为方法区编写管理代码的麻烦.  

        但是这种设计导致了一种问题, 那就是方法区内存溢出, 因为永久代有-XX:MaxPermSize的上限,即使不设置也有默认大小.

        考虑到HotSpot未来的发展,在JDK 6的时候HotSpot开发团队就有放弃永久代,逐步改为采用本地内存(Native Memory)来实现方法区的计划了. 

        到了JDK 7的HotSpot,已经把原本放在永久代的字符串常量池、静态变量等移出,而到了JDK 8,终于完全废弃了永久代的概念,改用与JRockit、J9一样在本地内存中实现的元空间(Meta-space)来代替

        根据虚拟机规范: 

  • 方法区的约束是非常宽松的,除了和Java堆一样不需要连续的内存和可以选择固定大小或者可扩展外,甚至还可以选择不实现垃圾收集

运行时常量池

        方法区的一部分. 在Class文件中, 除了类的版本, 字段, 方法, 接口等描述信息之外, 还有一项信息就是 常量池表, 用于存放编译期间生成的各种, 字面量和符号引用, 这部分内容将在类加载后存放到方法区的运行时常量池中.

        既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出OutOfMemoryError异常

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

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

相关文章

游戏如何应对云手机刷量问题

云手机的实现原理是依托公有云和 ARM 虚拟化技术,为用户在云端提供一个安卓实例,用户可以将手机上的应用上传至云端,再通过视频流的方式,远程实时控制云手机。 市面上常见的几款云手机 原本需要手机提供的计算、存储等能力都改由…

数据结构-3.2.栈的顺序存储实现

一.顺序栈的定义&#xff1a;top指针指向栈顶元素 1.图解&#xff1a; 2.代码&#xff1a; #include<stdio.h> #define MaxSize 10 //定义栈最多存入的元素个数 ​ typedef struct {int data[MaxSize]; //静态数组存放栈中元素int top; //栈顶指针 } SqStack; ​ int…

金手指设计

"MCP6294"。是一个轨到轨, 带宽为 10MHz 的 低功耗放大器. 对LM358测量 10MHz 范围内的频率特性&#xff0c;在 8MHz 左右&#xff0c;输出相移超过了 180。MCP6294的频率特性&#xff0c;则显示在 10MHz 运放相移之后 100左右。 对比两个运放的频率特性&#xff…

八股文-JVM

是什么&#xff1f;有什么用&#xff1f;谁发明的&#xff1f;什么时候发明的&#xff1f; Java虚拟机&#xff0c;用来运行Java程序&#xff0c;有很多个版本的虚拟机&#xff0c;比如HotSpot&#xff0c;最开始是SUN公司开发人员&#xff0c;和Java一起发布&#xff0c;现在…

通信工程学习:什么是PON无源光网络

PON&#xff1a;无源光网络 PON&#xff08;Passive Optical Network&#xff0c;无源光纤网络&#xff09;是一种采用光分路器等无源光器件进行信号传输和分配的光纤接入技术。它利用光纤作为传输媒介&#xff0c;通过无源设备将光信号从中心局&#xff08;如光线路终端OLT&am…

测试开发基础——软件测试中的bug

二、软件测试中的Bug 1. 软件测试的生命周期 软件测试贯穿于软件的整个生命周期 需求分析 测试计划 测试设计与开发 测试执行 测试评估 上线 运行维护 用户角度&#xff1a;软件需求是否合理 技术角度&#xff1a;技术上是否可行&#xff0c;是否还有优化空间 测试角度…

Android 15 正式发布至 AOSP

Google官方宣布&#xff0c;将于近期发布了 Android 15&#xff0c;而在早些时候&#xff0c;Google已经将其源代码推送至 Android 开源项目 (AOSP)。未来几周内&#xff0c;Android 15 将在受支持的 Pixel 设备上正式推出&#xff0c;并将于今年晚些时候在三星、Honor、iQOO、…

[vue2+axios]下载文件+文件下载为乱码

export function downloadKnowledage(parameter) {return axios({url: /knowledage/download,method: GET,params: parameter,responseType: blob}) }添加 responseType: blob’解决以下乱码现象 使用触发a标签下载文件 downloadKnowledage(data).then((res) > {let link …

高效处理NPE!!

相信不少小伙伴已经被java的NPE(Null Pointer Exception)所谓的空指针异常搞的头昏脑涨,有大佬说过“防止 NPE&#xff0c;是程序员的基本修养。”但是修养归修养&#xff0c;也是我们程序员最头疼的问题之一&#xff0c;那么我们今天就要尽可能的利用Java8的新特性 Optional来…

AI绘画与摄影新纪元:ChatGPT+Midjourney+文心一格 共绘梦幻世界

文章目录 一、AI艺术的新时代二、ChatGPT&#xff1a;创意的引擎与灵感的火花三、Midjourney&#xff1a;图像生成的魔法与技术的奇迹四、文心一格&#xff1a;艺术的升华与情感的共鸣五、融合创新&#xff1a;AI绘画与摄影实战的无限可能六、应用场景与实践案例AI艺术的美好未…

11 vue3之插槽全家桶

插槽就是子组件中的提供给父组件使用的一个占位符&#xff0c;用<slot></slot> 表示&#xff0c;父组件可以在这个占位符中填充任何模板代码&#xff0c;如 HTML、组件等&#xff0c;填充的内容会替换子组件的<slot></slot>标签。 匿名插槽 1.在子组…

一个场景是否可以同时选择CPU和GPU渲染

一个场景是否可以同时选择CPU和GPU渲染&#xff0c;主要取决于所使用的渲染软件及其支持的渲染引擎。在大多数情况下&#xff0c;现代渲染软件如3DMax等确实支持同时利用CPU和GPU进行渲染&#xff0c;以提高渲染效率和速度。 渲染软件的支持 以3DMax为例&#xff0c;它允许用…

xxl-job、Quartz、power-job、elastic-job对比选型

一、框架对比 1. Quartz 优点&#xff1a;稳定性和可扩展性好&#xff0c;适用于企业级应用&#xff1b;调度功能丰富&#xff0c;满足多种需求。 缺点&#xff1a;本身不提供原生的分布式支持&#xff0c;需要通过扩展或与其他组件结合来实现分布式任务调度&#xff1b;调度…

使用NotificationChannel实现后台视频上传

1、添加依赖 implementation net.gotev:uploadservice:4.8.0 implementation net.gotev:uploadservice-okhttp:4.8.02、在application中初始化服务&#xff1a; //初始化上传服务private fun initUploadService() {// 文件上传createNotificationChannel()//notificationChan…

算法里面的离散化

一、离散化&#xff08;discretization&#xff09;在算法和数据结构中指的是将连续的输入数据映射到离散的值或者范围&#xff0c;从而使得处理和计算变得更高效。通常用于处理大范围或者无限可能的输入&#xff0c;以便将其转化为有限的、可以有效处理的范围。 离散化的定义…

【深度学习】(3)--损失函数

文章目录 损失函数一、L1Loss损失函数1. 定义2. 优缺点3. 应用 二、NLLLoss损失函数1. 定义与原理2. 优点与注意3. 应用 三、MSELoss损失函数1. 定义与原理2. 优点与注意3. 应用 四、BCELoss损失函数1. 定义与原理2. 优点与注意3. 应用 五、CrossEntropyLoss损失函数1. 定义与原…

9.19总结

这几天学习了网络流 1&#xff0c;EK ek的主要思路是不断通过bfs找到增广路&#xff0c;找到增广路再建立反向边&#xff0c;直到不能再bfs到汇点&#xff0c;为什么可以通过建反向边呢&#xff1f;以上图举例&#xff0c;上图走完第一条增广路建立了一条反向边&#xff0c;当…

Maya动画基础

Maya动画基础教程&#xff08;完整&#xff09;_哔哩哔哩_bilibili 第一集 动画基础设置 altv播放动画 选择撕下副本 右键---播放预览 第二集 k帧记录物体的空间信息 初始位置清零 删除历史记录 s键key帧 自动记录位置信息 删除帧&#xff0c;按住右键选择delete 按shif…

Python if 语句优化技巧

大家好&#xff01;今天我们来聊聊Python中的if语句优化技巧。if语句是Python中最基本的控制结构之一&#xff0c;它用于根据条件执行不同的代码块。虽然if语句本身非常简单&#xff0c;但通过一些小技巧&#xff0c;可以让我们的代码更加高效、简洁。接下来&#xff0c;我们将…

LeetCode 算法笔记-第 04 章 基础算法篇

1.枚举 采用枚举算法解题的一般思路如下&#xff1a; 确定枚举对象、枚举范围和判断条件&#xff0c;并判断条件设立的正确性。一一枚举可能的情况&#xff0c;并验证是否是问题的解。考虑提高枚举算法的效率。 我们可以从下面几个方面考虑提高算法的效率&#xff1a; 抓住…