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

JVM专题六:JVM的内存模型
前面我们通过Java是如何编译、JVM的类加载机制、JVM类加载器与双亲委派机制等内容了解到了如何从我们编写的一个.Java 文件最终加载到JVM里的,今天我们就来剖析一下这个Java的‘中介平台’JVM里面到底长成啥样。 JVM的内存区域划分 Java虚拟机(JVM&…...

学习java第一百零七天
解释JDBC抽象和DAO模块 使用JDBC抽象和DAO模块,我们可以确保保持数据库代码的整洁和简单,并避免数据库资源关闭而导致的问题。它在多个数据库服务器给出的异常之上提供了一层统一的异常。它还利用Spring的AOP模块为Spring应用程序中的对象提供事务管理服…...

k8s上尝试滚动更新和回滚
滚动更新和回滚 实验目标: 学习如何进行应用的滚动更新和回滚操作。 实验步骤: 创建一个 Deployment。更新 Deployment 的镜像版本,观察滚动更新过程。回滚到之前的版本,验证回滚操作。 今天呢,我们继续来进行我们k…...

GitHub Copilot 登录账号激活,已经在IntellJ IDEA使用
GitHub Copilot 想必大家都是熟悉的,一款AI代码辅助神器,相信对编程界的诸位并不陌生。 今日特此分享一项便捷的工具,助您轻松激活GitHub Copilot,尽享智能编码之便利! GitHub Copilot 是由 GitHub 和 OpenAI 共同开…...

进程知识点(二)
文章目录 一、进程关系?二、孤儿态进程(Orphan)定义危害处理 三、僵尸进程定义处理 四、守护进程(Daemon )定义作用 总结 一、进程关系? 亲缘关系:亲缘关系主要体现于父子进程,子进程父进程创建,代码继承于父进程&…...

【线性代数】【一】1.6 矩阵的可逆性与线性方程组的解
文章目录 前言一、求解逆矩阵二、线性方程组的解的存在性总结 前言 前文我们引入了逆矩阵的概念,紧接着我们就需要讨论一个矩阵逆的存在性以及如何求解这个逆矩阵。最后再回归上最初的线性方程组的解,分析其中的联系。 一、求解逆矩阵 我们先回想一下在…...

基于大型语言模型的全双工语音对话方案
摘要解读 我们提出了一种能够以全双工方式运行的生成性对话系统,实现了无缝互动。该系统基于一个精心调整的大型语言模型(LLM),使其能够感知模块、运动功能模块以及一个具有两种状态(称为神经有限状态机,n…...

Spring Boot集成Minio插件快速入门
1 Minio介绍 MinIO 是一个基于 Apache License v2.0 开源协议的对象存储服务。它兼容亚马逊 S3 云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小&…...

【C++新特性】右值引用
右值和右值的区别 C11 中右值可以分为两种:一个是将亡值( xvalue, expiring value),另一个则是纯右值( prvalue, PureRvalue): 纯右值:非引用返回的临时变量、运算表达式产生的临时变…...

信息安全基础知识(完整)
信息安全基础知识 安全策略表达模型是一种对安全需求与安全策略的抽象概念表达,一般分为自主访问控制模型(HRU)和强制访问控制模型(BLP、Biba)IDS基本原理是通过分析网络行为(访问方式、访问量、与历史访问…...

QT
#include "widget.h" #include "ui_widget.h" Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) ,Gcancle(new QPushButton("取消",this)) ,EmmEdit(new QLineEdit(this)) { ui->setupUi(this);…...

双例集合(三)——双例集合的实现类之TreeMap容器类
Map接口有两个实现类,一个是HashMap容器类,另一个是TreeMap容器类。TreeMap容器类的使用在API上于HashMap容器类没有太大的区别。它们的区别主要体现在两个方面,一个是底层实现方式上,HashMap是基于Hash算法来实现的吗,…...

[SAP ABAP] 运算符
1.算数运算符 算术运算符描述加法-减法*乘法/除法MOD取余 示例1 输出结果: 输出结果: 2.比较运算符 比较运算符描述示例 等于 A B A EQ B <> 不等于 A <> B A NE B >大于 A > B A GT B <小于 A < B A LT B >大于或等于 A > B A GE B <小…...

MSPM0G3507 ——GPIO例程讲解2——simultaneous_interrupts
主函数: #include "ti_msp_dl_config.h"int main(void) {SYSCFG_DL_init();/* Enable Interrupt for both GPIOA and GPIOB ports */NVIC_EnableIRQ(GPIO_SWITCHES_GPIOA_INT_IRQN); //启用SWITCHES——A的中断 NVIC_EnableIRQ(GPIO_S…...

某程序员:30岁了,老婆管钱,背着我买了50万股票,亏了20w,强制她清仓后又买了36万
“辛辛苦苦攒了几年钱,本想买房买车,结果全被老婆炒股亏掉了!” 近日,一位30岁的程序员大哥在网上吐苦水,引发了网友们的热议。 这位程序员大哥和妻子结婚后,一直秉持着“男主外,女主内”的传统…...

Docker常见面试题整理
文章目录 1. Docker 是什么?它解决了什么问题?2. Docker 和虚拟机(VM)的区别是什么?3、Docker三个核心概念4、如何构建一个 Docker 镜像?5、如何将一个 Docker 容器连接到多个网络?6、Docker Co…...

35 - 最后一个能进入巴士的人(高频 SQL 50 题基础版)
35 - 最后一个能进入巴士的人 -- sum(weight) over(order by turn) as total,根据turn升序,再求前面数的和 selectperson_name from(selectperson_name,sum(weight) over(order by turn) as totalfromQueue) new_Queue wheretotal<1000 order by total desc lim…...

WPF将dll文件嵌入到exe文件中
WPF将dll文件嵌入到exe文件中 第一步:打开.csproj文件,在Import节点后添加如下代码: <Target Name"AfterResolveReferences"><ItemGroup><EmbeddedResource Include"(ReferenceCopyLocalPaths)" Condit…...

2024年AI+游戏赛道的公司和工具归类总结
随着人工智能技术的飞速发展,AI在游戏开发领域的应用越来越广泛。以下是对2024年AI+游戏赛道的公司和工具的归类总结,涵盖了从角色和场景设计到音频制作,再到动作捕捉和动画生成等多个方面。 2D与3D创作 2D创作工具:专注于角色和场景的平面设计,提供AI辅助的图案生成和风…...

svm和决策树基本知识以及模型评价以及模型保存
svm和决策树基本知识以及模型评价以及模型保存 文章目录 一、SVM1.1,常用属性函数 二、决策树2.1,常用属性函数2.2,决策树可视化2.3,决策树解释 3,模型评价3.1,方面一(评价指标)3.2&…...

C++ 79 之 自己写异常类
#include <iostream> #include <string> using namespace std;class MyOutOfRange : public exception{ // 选中exception右键 转到定义 复制一份 virtual const char* what() const _GLIBCXX_TXN_SAFE_DYN _GLIBCXX_NOTHROW 进行函数重写 public: string m_msg;M…...

如何搭建一个成功的短剧制作平台
要搭建一个成功的短剧制作平台,需要考虑多个方面,包括目标定位、技术选择、内容管理、用户体验等。 1、明确目标和定位: 确定你的目标受众是谁,他们的年龄、兴趣、消费习惯等。 明确短剧制作平台的主要定位,是提供原创…...

kotlin类
一、定义 1、kotlin中使用关键字class 声明类,如果一个类没有类体,也可以省略花括号, 默认为public 类型的: // 这段代码定义了一个公开的、不可被继承的Test类 class Test{} // 没有类体,可以省略花括号 class Test 底层代码&…...

android | studio的UI布局和代码调试 | UI调试 (用于找到项目源码)
网上找到一个项目,想快速的搞懂是怎么实现的,搞了半天发现原来android都升级到Jetpack Compose了,然后去找源码挺不容易的,摸索中发现了这个调试的方法,还可以。 https://developer.android.com/studio/debug/layout-i…...

LangChain实战技巧之六:一起玩转config(上篇)——ConfigurableField
简介 Config 包含两大类内容, ConfigurableField 可配置的字段 configurable_alternatives 可配置的替代方案 分别使用两篇文章来给大家介绍,本篇先介绍ConfigurableField 常规介绍 一些资料会这样介绍 model_spec model.configurable_fields(model…...

扫码称重上位机
目录 一 设计原型 二 后台代码 一 设计原型 模拟工具: 二 后台代码 主程序: using System.IO.Ports; using System.Net; using System.Net.Sockets; using System.Text;namespace 扫码称重上位机 {public partial class Form1 : Form{public Form1(){Initialize…...

操作系统入门 -- 进程的通信方式
操作系统入门 – 进程的通信方式 1.什么是进程通信 1.1 定义 进程通信就是在不同进程之间交换信息。在之前文章中可以了解到,进程之间相互独立,一般不可能互相访问。因此进程之间若需要通信,则需要一个所有进程都认可的共享空间࿰…...

Python读取wps中的DISPIMG图片格式
需求: 读出excel的图片内容,这放在微软三件套是很容易的,但是由于wps的固有格式,会出现奇怪的问题,只能读出:类似于 DISPIMG(“ID_2B83F9717AE1XXXX920xxxx644C80DB1”,1) 【该DISPIMG函数只有wps才拥有】 …...

elasticsearch的入门与实践
Elasticsearch是一个基于Lucene构建的开源搜索引擎。它提供了一个分布式、多租户能力的全文搜索引擎,具有HTTP web接口和无模式的JSON文档。以下是Elasticsearch的入门与实践的基本步骤: 入门 安装Elasticsearch: 从Elasticsearch官网下载对…...

神经网络学习6-线性层
归一化用的较少 正则化用来解决过拟合,处理最优化问题,批量归一化加快速度 正则化(Regularization): 作用:正则化是一种用来防止过拟合的技术,通过向模型的损失函数中添加惩罚项,使…...