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划分了各个内存区域放各种数据,随着程序的运行或者数据量变多了,内存放不下了怎么办呢?或者在分配内存的时候大家地址重复了又该咋整呢?

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

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

相关文章

使用 Ubuntu x86_64 平台交叉编译适用于 Linux aarch64(arm64) 平台的 QT5(包含OpenGL支持) 库

使用 Ubuntu AMD64 平台交叉编译适用于 Linux ARM64 平台的 QT5(包含 OpenGL/WebEngine 支持) 库 目录 使用 Ubuntu AMD64 平台交叉编译适用于 Linux ARM64 平台的 QT5(包含 OpenGL/WebEngine 支持) 库写在前面前期准备编译全流程1. 环境搭建2. 复制源码包并解压,创…

【Python机器学习】NMF——将NMF应用于人脸图像

将NMF应用于之前用过的Wild数据集中的Labeled Faces。NMF的主要参数是我们想要提取的分量个数。通常来说,这个数字要小于输入特征的个数(否则的话,将每个像素作为单独的分量就可以对数据进行解释)。 首先,观察分类个数…

细胞核的分割与分类模型·HoVer-Net|动手实操

小罗碎碎念 上一期推文已经介绍了hover net的背景和代码仓库情况,这一期则是根据作者提供的示例代码进行分析,详细你看完这一期推文,应该就能大致掌握这些套路了。如果觉得意犹未尽,那就等待下一期吧,哈哈。 一、编程…

Vue3 + TS + Antd + Pinia 从零搭建后台系统(四) ant-design-vue Layout布局,导航栏,标签页

书接上回本篇主要介绍: Layout布局,导航栏,标签页继续填充目录 按需引入组件Layout布局,导航栏,标签页css样式 按需引入组件 使用unplugin-vue-components插件完成ant-design-vue组件的按需加载。 前文中已处理过&…

运营管理和服务支撑阶段

我前面的所有设备都部署好了,现在就需要运营管理和服务支撑 遇到问题了迅速解决,避免风险扩大 我们也可以给客户提供上面的服务,提高客户的预警能力,安全风险处理能力 我们不仅提供设备,还提供服务 我们公司成立了安…

高考填报志愿选专业,要善于发掘自身优势

每年的高考季,如何填报志愿又再成为困扰家长以及学生的难题,可能在面对大量的专业时,无论是考生还是家长都不知道应该如何选择,好的专业孩子不一定有优势,感兴趣的冷门专业又担心日后找工作难。 实际上,专业…

React+TS前台项目实战(十六)-- 全局常用组件Pagination封装

文章目录 前言Pagination组件1. 功能分析2. 代码详细注释3. 使用方式4. 效果展示 [PC端&手机端] 总结 前言 在上篇文章中,我们封装了表格组件Table,本文则继续封装配套使用的分页器组件。想看Table表格组件的,可自行查看全局常用组件Tab…

QuantML-Qlib Model | Kansformer: KAN+Transformer时序模型用于股票收益率预测

QuantML-Qlib Model | Kansformer: KANTransformer时序模型用于股票收益率预测 原创 QuantML QuantML 2024-06-18 20:57 上海 Content 之前公众号介绍了几篇KAN的文章,也做过KAN相关的模型: What KAN I say?KAN代码全解析 Qu…

胖东来启示录:传统商超如何逆境求生?

近日,经过胖东来精心调改的永辉超市郑州信万广场店盛大开业,首日销售额高达188万元,客流量突破1.2万人,业绩飙升13.9倍,这一惊人数据无疑为当前低迷的传统商超行业带来了一线生机。胖东来,这位零售业的黑马…

java—类反射机制

简述 反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息(如成员变量,构造器,成员方法等),并能操作对象的属性及方法。反射机制在设计模式和框架底层都能用到。 类一旦加载,在堆中会产生…

Java程序之让气球上升

问题: ACM比赛时间再次举行!看到气球四处漂浮是多么的兴奋啊。但要告诉你一个秘密,评委们最喜欢的时间是猜测最流行的问题。比赛结束后,他们会数出每种颜色的气球,然后找到结果。今年,他们决定把这份可爱的…

【建设方案】基于gis地理信息的智慧巡检解决方案(源文件word)

传统的巡检采取人工记录的方式,该工作模式在生产中存在很大弊端,可能造成巡检不到位、操作失误、观察不仔细、历史问题难以追溯等现象,使得巡检数据不准确,设备故障隐患得不到及时发现和处理。因此建立一套完善的巡检管理系统是企…

Java程序之寻找自幂数

题目: 自幂数是指一个 n 位数(3≤n≤7 ),它的每个位上的数字的 n 次幂之和等于它本身(例如:1^3 5^3 3^3 153;1^46^43^44^41634)。三位自幂数:水仙花数;四位…

HeidiSQL导入与导出数据

HeidiSQL两种导入与导出数据的方法:整个库复制,和仅复制数据 一 整个库复制 1 选中需要导出的数据库(这里是MyDBdata),点击导出为SQL脚本。 2 按照如图进行选择 3 选做:删除当前数据库【如果有】 -- 删除数据库 USE mysql; D…

python-题库篇-Python语言特性

文章目录 Python语言特性1 Python的函数参数传递2 Python中的元类(metaclass)3 staticmethod和classmethod4 类变量和实例变量5 Python自省6 字典推导式7 Python中单下划线和双下划线8 字符串格式化:%和.format9 迭代器和生成器10 *args and **kwargs11 面向切面编程AOP和装饰器…

基于SpringBoot+大数据城市景观画像可视化设计和实现

💗博主介绍:✌全网粉丝10W,CSDN作者、博客专家、全栈领域优质创作者,博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌💗 🌟文末获取源码数据库🌟 感兴趣的可以先收藏起来,…

C语言入门系列:初识函数

文章目录 一,C语言函数与数学函数的区别1,回忆杀-初中数学2,C语言中的函数 二, 函数的声明1,函数头1.1,函数名称1.2,返回值类型1.3,参数列表 2,函数体2.1,函数…

Android使用zxing生成二维码

效果图如下: **前提:导入zxing的jar后开始操作,老规矩最后有源码,作者布局默认相对布局。 第一步:定义二维码的长宽高及图片控件** 第二步:实例化QRCodeWriter后利用for循环将二维码画出来,然后…

用Visual Studio调试CMake项目并生成Visual Studio工程

一. 在Windows系统上安装CMake 访问CMake官方网站https://cmake.org/download,或通过文末链接下载:在下载页面上,找到并点击“Download”链接以获取最新的稳定版本的CMake。请注意,虽然新版本可能包含更多功能和改进,…

关于DrawTools的分析- 一个优秀的C#开源绘图软件

国外大佬,曾经写过两个关于DrawTools相关的开源绘图软件。 我更新了一个优化的版本如下图,稍后会发布更新给大家。 需要的用户可发邮件给我 448283544qq.com 应用于AGV地图编辑器如下: 那么这个优于很多普通的画布软件,包含点、…