JVM专题六:JVM的内存模型

前面我们通过Java是如何编译、JVM的类加载机制、JVM类加载器与双亲委派机制等内容了解到了如何从我们编写的一个.Java 文件最终加载到JVM里的,今天我们就来剖析一下这个Java的‘中介平台’JVM里面到底长成啥样。

JVM的内存区域划分

Java虚拟机(JVM)是Java程序运行的虚拟计算机,它负责将Java字节码转换为特定平台上的机器指令。JVM的内存区域划分是JVM规范中定义的,它规定了JVM在执行Java程序时所管理的不同内存区域。JVM内存区域的主要划分如下图所示:

方法区&&元空间

        用于存储类信息、常量、静态变量等数据的内存区域。它是所有线程共享的,用于支持类和接口的组织。在HotSpot JVM中,它通常被实现为永久代或元空间。从Java 8开始替代了永久代,用于存储类的元数据,如类的静态结构。元空间不是JVM堆的一部分,而是使用本地内存,有助于避免内存溢出错误。

Java堆内存

        堆是用于存储对象实例和数组的内存区域,是垃圾回收器管理的主要区域,也是所有线程共享的。堆通常分为新生代和老年代,垃圾收集器定期清理无用对象以回收内存。

线程栈内存

         栈是每个线程独有的内存区域,用于存储局部变量、操作栈和方法调用信息。栈由栈帧组成,每个栈帧包含局部变量表、操作数栈、动态链接信息和方法返回地址。

本地方法栈

        本地方法栈用于支持JVM使用本地方法时的内存管理,类似于Java栈,但是用于管理本地方法调用。

程序计数器

        程序计数器是每个线程都有一个的内存区域,用于记录当前线程执行的字节码的行号指示器。线程切换时,程序计数器也会切换到下一个方法的起始点。

直接内存

        直接内存虽然不是JVM运行时数据区的一部分,但是Java NIO允许使用直接内存进行高效的I/O操作。直接内存不是由JVM管理,但是可以通过Java代码进行分配和释放。

        每个内存区域都有其特定的用途和GC行为,了解这些区域可以更好地理解Java程序的内存管理和性能调优,为后续我们谈论GC相关做好铺垫。

JVM内存模型实例

        老样子,上一章节我们从理论介绍来JVM内存区域划分及其各个区域应该存放的数据下面还是通过一段简单的代码来一起探究这个过程中数据是怎样流转的?

public class App {

    public static void main(String[] args) {
        SpringApplication sApp = new SpringApplication();
        // sApp.run(App.class, args);
    }

}

1、存放类的方法区&&元空间

其实在jdk1.8以后改成元空间更加利于我们的理解了,元空间存放元数据的空间,对于Java虚拟机谁是他的元数据呢?当然是class相关的数据呢,所以元空间(方法区当然是存放类相关数据的呢)。上述代码在JVM中的大概位置如图:

2、程序计数器

通过前面Java编译大家可以明白:我们编写好的源代码,会被编译成各种字节码指令,然后字节码指令会被一条一条的执行。所以JVM在加载class文件后需要有个可以执行字节码指令的工具,在JVM中这个工具就是字节码执行引擎。

但是字节码执行引擎只是用来执行指令的,具体执行到哪里了就需要另外程序计数器来记录。如下图所示:

Java虚拟机(JVM)是支持多线程的,它允许多个线程并发执行。在Java中,每个线程都有自己的程序计数器(Program Counter,PC),这个计数器是线程私有的。程序计数器用于存储当前线程正在执行的字节码指令的地址,确保线程在执行过程中能够正确地跟踪执行状态。

因此每一个线程执行字节码时,程序计数器会指向当前正在执行的指令。如果线程被暂停或阻塞,程序计数器会保持在当前指令的位置,这样当线程再次被调度执行时,可以从上次暂停的地方继续执行。

 如下图更加准确描述了他们之间的关系:

在Java中,代码的执行总是由线程来驱动的。即使是简单的main()方法,也是由JVM在启动时创建的名为main的线程来执行的。这个线程是Java程序的入口点,它负责执行main()方法中的代码。当main线程开始执行main()方法时,它的程序计数器会记录当前执行的字节码指令的地址。程序计数器是每个线程私有的内存区域,它的作用是确保线程能够跟踪其执行状态,包括当前执行到哪一行代码或哪一个字节码指令。

3、Java虚拟机栈 

上面介绍的程序计数器是用来记录指令执行的位置的且与每个线程有关,但是我们知道其实除了类变属性以外,其实每个方法都有自己的局部变量如下是的代码示例:

public class App {
    public static void main(String[] args) {
        SpringApplication sApp = new SpringApplication();
        //sApp.run(App.class,args);
        sApp.getRunListeners(args);
    }
}



public class SpringApplication {

    public String run(Class appClass, String[] args) {
        System.out.println("my Spring Application ");
        return "run args";
    }

    public String getRunListeners( String[] args) {
        System.out.println("My  getRunListeners ");
        String myRunListenersVar = "myRunListenersVar";
        this.getSpringFactoriesInstances(args);
        return "run args";
    }

    public String getSpringFactoriesInstances( String[] args) {
        System.out.println("My  getSpringFactoriesInstances ");
        String mySpringFactoriesInstancesVar = "mySpringFactoriesInstances";

        return "run args";
    }


}

上述代码SpringApplication类的getRunListeners方法与getSpringFactoriesInstances方法都有各自的局部变量,因此Java虚拟机也必须有一块内存空间去存该部分数据。

上述代码运行mian方法,会将首相会将App类里的main作为第一个栈帧压倒main线程所在的Java虚拟机栈,如下图所示:

进入SpringApplication#getRunListeners方法时,会将getRunListeners作为第二个栈帧压入main线程所在的Java虚拟机栈,同时在getRunListeners我们声明里局部变量myRunListenersVar变量,该变量在栈帧getRunListeners里。如下图所示:

方法继续向下执行,进入getSpringFactoriesInstances方法,会将getSpringFactoriesInstances压入main线程的Java虚拟机栈顶,如下图所示:

因为Java栈结构是先进后出,后续便会按照getSpringFactoriesInstances -> getRunListeners -> main 顺序进行弹栈,知道main方法运行结束。所以局部变量只会在方法内部生效,同时Java虚拟机栈又是线程内执行,所以后续介绍Java并发编程的时候我们说线程安全的时候也会提到,局部变量避免并发冲突等。

介绍完上述内容,我们再用一张图描述下:

4、Java堆内存

在介绍栈的过程中有意识的跳过了上述代码中关于创建SpringApplication这块的代码,我们再回头看看这段代码。

public class App {
    public static void main(String[] args) {
        SpringApplication sApp = new SpringApplication();
  
    }
}

上述 new SpringApplication()代码就是创建了一个SpringApplication对象实例,同样作为JVM也需要找个地方来存放对象实例数据,而这个地方被称作为Java堆内存。

当我们创建一个对象的时候,会将这个对象的实例数据放到堆内存中,然后把这个实例存放的引用返回给局部变量,这样我们就持有了对象实例的地址。

还是画一张图更加清晰一点:

5、整体流程

介绍整体流程之前,其实还有个区域就是Java为了区分我们写的方法和自己内置调用的方法,专门用来处理本地方法的调用区域管理,这块JVM称之为本地方法栈。至此整个流程就可以完整画出来了。

1、你的JVM进程会启动,就会先加载App类到内存里。然后有一个main线程,开始执行你的App中的main()方法。main线程是关联了一个程序计数器的,那么他执行到哪一行指令,就会记录在这里
2、main线程在执行main()方法的时候,会在main线程关联的Java虚拟机栈里,压入一个main()方法的栈帧。
3、接着会发现需要创建一个SpringApplication类的实例对象,此时会加载SpringApplication.class文件到内存里来
4、创建一个SpringApplication的对象实例分配在Java堆内存里,并且在main()方法的栈帧里的局部变量表引入一个sApp”变量,让他引用SpringApplication对象在Java堆内存中的地址。
5、main线程开始执行SpringApplication对象中的方法,会依次把自己执行到的方法对应的栈帧压入自己的Java虚拟机栈
6、执行完方法之后再把方法对应的栈帧从Java虚拟机栈里弹出来,

那么JVM中的各个核心内存区域的功能和对应的我们的Java代码之间的关系,就彻底理解

其实到这里JVM相关应该就结束了,但是在上面理论介绍的时候我们还提及到了直接内存,其实这块放到IO相关模块更加合适,上述提及到是为了给大家理解元空间做的实例。

同样最后的最后我们思考一两个问题,上述我们给JVM划分了各个内存区域放各种数据,随着程序的运行或者数据量变多了,内存放不下了怎么办呢?或者在分配内存的时候大家地址重复了又该咋整呢?

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

51单片机STC89C52RC——6.1 中断系统

一,文字层面理解 反正我看下面的几段文字时脑壳没有正常运转。一个头几个大 中断系统是为使CPU具有对外界紧急事件的实时处理能力而设置的。 当中央处理机CPU正在处理某件事的时候外界发生了紧急事件请求,要求CPU暂停当前的工作,转而去处理这…

springboot优雅shutdown时异步线程安全优化

前面针对graceful shutdown写了两篇文章 第一篇: https://blog.csdn.net/chenshm/article/details/139640775 只考虑了阻塞线程,没有考虑异步线程 第二篇: https://blog.csdn.net/chenshm/article/details/139702105 第二篇考虑了多线程的安全…

Linux DNS配置文档

一、问题描述 1. 无法在浏览器通过域名访问百度; 2. 无法在终端 ping 通百度,例如:ping www.baidu.com 3. 可以 ping 通公网地址,例如:ping 114.114.114.114 或 ping 8.8.8.8 二、问题原因 域名解析 DNS 配置错误&am…

如何快速绘制logistic回归预测模型的ROC曲线?

临床预测模型,也是临床统计分析的一个大类,除了前期构建模型,还要对模型的预测能力、区分度、校准度、临床获益等方面展开评价,确保模型是有效的! 其中评价模型的好坏主要方面还是要看区分度和校准度,而区分…

C++初学者指南第一步---12.引用

C初学者指南第一步—12.引用 文章目录 C初学者指南第一步---12.引用1. 功能(和限制)1.1 非常量引用1.2 常量引用1.3 auto引用 2.用法2.1 范围for循环中的引用2.2 常量引用的函数形参2.3 非常量引用的函数形参2.4 函数参数的选择:copy / const…

62.WEB渗透测试-信息收集- WAF、框架组件识别(2)

免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动! 内容参考于: 易锦网校会员专享课 上一个内容:61.WEB渗透测试-信息收集- WAF、框架组件识别(1) 打开一个搜索引…

有趣的 Oracle JDBC 驱动包命名问题 - ojdbc6 和 ojdbc14 哪个新?!

有趣的 Oracle JDBC 驱动包命名问题 - ojdbc6 和 ojdbc14 哪个新?! 1 背景概述 最近协助一个小兄弟排查了某作业使用 sqoop 采集 oracle 数据的失败问题,问题现象,问题原因和解决方法都挺直观,但在此过程中发现了一个有趣的 Oracle JDBC 驱…

mechanize - 自动化与HTTP web服务器的交互操作

1、前言 随着自动化测试的普及与落地推广,出现了众多知名的自动化测试工具,如Selenium 、Robot Framework、Playwright等。本文将介绍一款在Python环境下的mechanize库,这个库能够模拟浏览器行为,支持发送HTTP请求、解析HTML页面和…

【2024.6.23】今日 IT 速递 | 亚布力创新年会热点新闻盘点

人不走空 🌈个人主页:人不走空 💖系列专栏:算法专题 ⏰诗词歌赋:斯是陋室,惟吾德馨 目录 🌈个人主页:人不走空 💖系列专栏:算法专题 ⏰诗词歌…

Vue3+TypeScript项目实战——打造雨雪交加的智慧城市

个人简介 👀个人主页: 前端杂货铺 ⚡开源项目: rich-vue3 (基于 Vue3 TS Pinia Element Plus Spring全家桶 MySQL) 🙋‍♂️学习方向: 主攻前端方向,正逐渐往全干发展 &#x1…

leetcode 二分查找·系统掌握

题目: 题解: 在阶梯数达到某一值后已有的硬币数量就小于了阶梯可以装的硬币数量,根据题意可以使用~10~泛型查找出最后一个可以被填满的阶梯。对于这类型可以二分答案的题目关键在于二分答案的上下界,本题的下界就是1上界就是硬币…

内容安全复习 8 - 视觉内容伪造与检测

文章目录 研究背景内容伪造方法虚假人脸生成人脸替换属性编辑表情重演跨模态人脸编辑 伪造检测方法眨眼检测交互式人脸活体检测一些了解方法挑战 研究背景 图像内容篡改造成新闻报道的偏颇易导致社会和公共秩序的不安,对公共安全产生不良影响。 造成的影响&#x…

英伟达能保住全球市值第一的桂冠吗?

内容提要 《巴伦周刊》认为,英伟达市值的迅速上涨是该公司可能难以保持市值第一桂冠的关键原因。另一个担忧是,英伟达的崛起主要基于一项单一技术——为人工智能应用提供动力的芯片和平台。一些人担心,如果购买英伟达产品的公司无法从投资中…

Open MMLab 之 MMDetection框架

MMDetection框架入门教程(完全版)-CSDN博客 OpenMMLab MMDetection是商汤和港中文大学针对目标检测任务推出的一个开源项目,它基于Pytorch实现了大量的目标检测算法,把数据集构建、模型搭建、训练策略等过程都封装成了一个个模块…

域名防红程序网站源码-最新可用

网上泛滥的都是2.5的版本,这是2.7的版本! 功能简介 解决QQ内报毒问题,直接跳浏览器操作,好像这个版本只能安卓QQ了,最新版的支持IOS QQ。 url.cn 大绿标功能!此源码仅供测试使用! 安装说明 …

如何获取特定 HIVE 库的元数据信息如其所有分区表和所有分区

如何获取特定 HIVE 库的元数据信息如其所有分区表和所有分区 1. 问题背景 有时我们需要获取特定 HIVE 库下所有分区表,或者所有分区表的所有分区,以便执行进一步的操作,比如通过 使用 HIVE 命令 MSCK REPAIR TABLE table_name sync partiti…

【C语言】关于字符串函数的使用及模拟实现(1)

一、字符串追加 1.1 库函数srecat的使用 1.2 库函数strncat的使用 1.3 模拟实现库函数 strcat 及 strncat 由上可知,字符串追加的原理是找到所添加字符串的 \0 位置,再对其进行添加。 代码1、 代码2、 二、字符串查找 2.1 库函数strstr的使用 使用…

Day28:回溯法 491.递增子序列 46.全排列 47.全排列 II 332.重新安排行程 51. N皇后 37. 解数独

491. 非递减子序列 给你一个整数数组 nums ,找出并返回所有该数组中不同的递增子序列,递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。 数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情…

用含成员函数的类,分别输入和输出各对象中的时间(时:分:秒)

编写程序: 运行结果: 注意: (1)在主函数中调用两个成员函数时,应指明对象名(t1,t2)。表示调用的是哪一个对象的成员函数。t1.display()和t2.display()虽然都是调用同一个 display函数,但…

最长考拉兹序列

题目: 考虑如下定义在正整数集上的迭代规则: n n/2 (若n为偶数) n 3n1 (若n为奇数) 从13开始,可以迭代生成如下的序列: 13 40 20 10 5 16 8 4 2 1 可以看出这个序列(从13…