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.mzph.cn/diannao/54057.shtml

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

相关文章

[产品管理-23]:NPDP新产品开发 - 21 - 产品创新中的市场调研 - 市场调研对创新产品开发的意义

目录 前言: 一、市场调研概述 1.1 客户与市场的区别 1、定义与范围 2、关注焦点 3、作用与影响 4、总结 1.2 销售与市场的区别 1、对象与范围 2、工作方式 3、工作内容 4、目标 5、考核标准 6、在企业运营中的角色 1.3 什么是市场调研 1、市场调研的…

COMDEL电源维修CLX2500康戴尔射频电源维修

美国COMDEL射频电源维修常见型号包括:CLX2750;CLX2500;CLX-600H;CX600AS;CX-5000S;CX-3500S;CX-2500S;CV500;CDX2000等。 Comdel成立于1966年,总部设在马萨诸…

Golang | Leetcode Golang题解之第406题根据身高重建队列

题目&#xff1a; 题解&#xff1a; func reconstructQueue(people [][]int) (ans [][]int) {sort.Slice(people, func(i, j int) bool {a, b : people[i], people[j]return a[0] > b[0] || a[0] b[0] && a[1] < b[1]})for _, person : range people {idx : pe…

maya-vray渲染蒙版

要用一个叫vrayMulWrapper的材质球&#xff0c;把alpha Conterbution调到-1&#xff0c;勾选matte surface启用蒙版物体。

【GESP】C++一级练习BCQM3005,基本输出语句printf

一道基础练习题&#xff0c;练习基本输出语句printf。 BCQM3005 题目要求 描述 输出表达式1234∗5678的结果。 输入 无 输出 1234∗56787006652 输入样例 无 输出样例 1234 * 5678 7006652 全文详见个人独立博客&#xff1a;https://www.coderli.com/gesp-1-bcqm3005/ 【…

在树莓派上构建和部署 Node.js 项目

探索在Raspberry Pi上构建和部署Node.js项目的最佳实践。通过我们的专业提示和技巧&#xff0c;克服常见挑战&#xff0c;使您的项目顺利运行。 去年圣诞节&#xff0c;我收到了一份极其令人着迷的礼物&#xff0c;它占据了我许多周末的时间&#xff0c;甚至让我夜不能寐。它就…

零基础考过软考信息系统项目管理师经验分享

选择适合的课程&#xff1a;如果你是零基础&#xff0c;建议找一些专门针对新手的课程&#xff0c;讲解通俗易懂。 刷题至关重要&#xff1a;软考的题库很庞大&#xff0c;多做题是必须的。 做好笔记和复习&#xff1a;上课时要做好笔记&#xff0c;课后及时复习&#xff0c;…

网络安全学习(二)初识kali

kali有两种界面模式&#xff0c;为了更好的适应windows用户&#xff0c;需要操作一下。 先更新一下kali&#xff0c;执行命令 sudo apt-get update 然后换界面 sudo apt install kali-desktop-gnome 等待&#xff0c;出现如下界面时&#xff0c;选择gdm3&#xff08;键盘&a…

Mybatis中Like模糊查询三种处理方式

目录 Mybatis中Like模糊查询三种处理方式 1.通过单引号拼接${} 1&#xff09;mapper接口 2&#xff09;Mapper.xml 3&#xff09;测试代码 4) 测试结果 2.通过concat()函数拼接(个人推荐使用这种) 1&#xff09;mapper接口 2&#xff09;Mapper.xml 3&#xff09;测试代码 4) 测…

Requests-HTML模块怎样安装和使用?

要安装和使用Requests-HTML模块&#xff0c;您可以按照以下步骤进行操作&#xff1a; 打开命令行界面&#xff08;如Windows的命令提示符或Mac的终端&#xff09;。 使用pip命令安装Requests-HTML模块。在命令行中输入以下命令并按回车键执行&#xff1a; pip install request…

工厂模式(一):简单工厂模式

一、概念 顾名思义&#xff0c;带着工厂&#xff0c;两字肯定就是有标准、快速、统一等等一些工厂独有的特点。 那么什么是简单工厂模式呢&#xff1f; 定义&#xff1a;简单工厂模式是一种创建对象的设计模式&#xff0c;它定义了一个工厂类通过某个静态方法来生成不同类型的…

熵权法详细讲解+Python代码实现

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

C#/.NET/.NET Core技术前沿周刊 | 第 5 期(2024年9.9-9.15)

前言 C#/.NET/.NET Core技术前沿周刊&#xff0c;你的每周技术指南针&#xff01;记录、追踪C#/.NET/.NET Core领域、生态的每周最新、最实用、最有价值的技术文章、社区动态、优质项目和学习资源等。让你时刻站在技术前沿&#xff0c;助力技术成长与视野拓宽。 欢迎投稿&…

Docker安装mysql安装nginx安装Redis

Docker安装mysql 下载镜像 docker pull mysql:8.0注意,使用此方法安装镜像需要提前配置镜像源,详情看之前的文章 安装 docker run -d -p 3306:3306 \ --name mysql \ --restartalways \ --privilegedtrue \ -e TZAsia/Shanghai \ -e MYSQL_ROOT_PASSWORDroot \ mysql:8.0进…

[网络]https的概念及加密过程

文章目录 一. HTTPS二. https加密过程 一. HTTPS https本质上就是http的基础上增加了一个加密层, 抛开加密之后, 剩下的就是个http是一样的 s > SSL HTTPS HTTP SSL 这个过程, 涉及到密码学的几个核心概念 明文 要传输的真正意思是啥 2)密文 加密之后得到的数据 这个密文…

【南方科技大学】CS315 Computer Security 【Lab2 Buffer Overflow】

目录 引言软件要求启动虚拟机环境设置禁用地址空间布局随机化&#xff08;ASLR&#xff09;设置编译器标志以禁用安全功能 概述BOF.ctestShellCode.c解释 createBadfile.c 开始利用漏洞在堆栈上查找返回地址 实验2的作业 之前有写过一个 博客&#xff0c;大家可以先看看栈溢出…

【裸机装机系列】2.kali(ubuntu)-裸机安装kali并进行磁盘分区-2024.9最新

【前言】 2024年为什么弃用ubuntu,请参考我写的另一篇博文&#xff1a;为什么不用ubuntu&#xff0c;而选择基于debian的kali操作系统-2024.9最新 【镜像下载】 1、镜像下载地址 https://www.kali.org/get-kali/选择installer-image&#xff0c;进入界面下载相应的ISO文件 我…

【Android 13源码分析】WindowContainer窗口层级-2-构建流程

在安卓源码的设计中&#xff0c;将将屏幕分为了37层&#xff0c;不同的窗口将在不同的层级中显示。 对这一块的概念以及相关源码做了详细分析&#xff0c;整理出以下几篇。 【Android 13源码分析】WindowContainer窗口层级-1-初识窗口层级树 【Android 13源码分析】WindowCon…

学习整理vue前端框架项目目录结构的含义

学习整理vue前端框架项目目录结构的含义 1、目录结构2、结构含义 1、目录结构 2、结构含义

EasyExcel拿表头(二级表头)爬坑,invokeHeadMap方法

OK&#xff0c;不废话&#xff0c;直接开干!说实话是有些坑&#xff0c;或者是我不会用吧 模板如下&#xff1a; invokeHeadMap 这个方法其实针对第一行就是表头的完全没问题。针对第二行的&#xff0c;我DEBUG拿到的是这样很明显&#xff0c;他拿到了第一行&#xff1b;既然…