JVM(Java Virtual Machine)内存模型篇

前言

本文是JVM系列的内存模型篇,参考资料为《深入理解Java虚拟机》,本文章将会以HotSpot 虚拟机为介绍基础。

1.JVM简单介绍

Java Virtual Machine是运行Java程序的基础,JVM基于C、C++实现,JVM有很多种类,但是这些虚拟机都必须按照《Java虚拟机规范》来进行实现。目前JDK使用的是HotSpot虚拟机。

2.JVM内存模型

根据《Java虚拟机规范》的规定,Java虚拟机所管理的内存将会包括以下几个运行时数据区域

  • 程序计数器
  • Java虚拟机栈
  • 本地方法栈
  • 方法区

分布如下图:
在这里插入图片描述

3.程序计数器

程序计数器是Java中占用内存比较少的一个区域,他的作用是记录当前线程所执行的字节码的行号指令,通俗的理解就是代码执行到哪里了

我们很容易思考到,在多线程中,是发生线程切换这种情况的,那么一个线程被切换后,它的状态就需要被记录到上下文中,方便线程能正确执行到原来的位置,那么为了记录这个位置,就需要程序计数器来进行实现

为了线程切换后能恢复到正确的执行位置,每个线程都需要一个独立程序计数器,各个线程之间计数器互不影响,独立存储。因此它也是“线程私有”的内存

这片区域也是唯一一个在《Java虚拟机规范》中没有任何OutOfMemoryError情况的区域。

4.Java虚拟机栈

Java虚拟机栈,以“栈”命名的,在内存模型中,基本都是用来处理方法的,所以Java虚拟机栈是用来处理Java语言实现的方法的。同理的,这个栈也是线程私有的。他的生命周期与线程生命周期一样长。

一个线程在调用方法的时候,会在虚拟机栈中,创建一个栈帧,这个栈帧会存放局部变量表、操作数栈、动态连接、方法出口等信息。
在这里插入图片描述
栈帧包含以下内容:

  • 局部变量表: 栈帧用于存储方法的局部变量,表中存放了编译期可知的基本数据类型和对象引用(对象引用指针或者句柄)。这些局部变量在方法调用时分配内存空间,并在方法调用结束后被释放。

  • 操作数栈: 栈帧还包含一个操作数栈,用于存储方法执行时的操作数。当方法需要进行计算或操作时,操作数会被入栈或出栈。

  • 动态链接: 栈帧包含指向运行时常量池中当前方法引用的指针,用于在方法中访问其他类或方法。

  • 方法出口: 当方法调用完成后,程序需要返回到方法调用的地方继续执行。栈帧包含方法返回地址,用于记录返回的位置。

额外提一嘴的是当Java虚拟机栈的深度被方法调用填满的时候,就会出现StackOverFlowError;如果栈的大小动态扩展到没办法扩展的时候,会报OOM(OutOfMemoryError)的错误。

5.本地方法栈

这个栈和Java虚拟机栈是一样的功能,但是作用的对象不一样,Java虚拟机栈对应的是Java方法,而本地方法栈对应的是被Native标志的方法,这类方法一般都是C、C++代码。其他东西基本和Java虚拟机栈一致。

6.方法区

方法区与Java堆一样,是各个线程共享的内存区域,这块区域是用来存储已经被加载的类元信息,这些信息包含:类型信息、常量、静态变量、即使编译后的代码缓存等信息。

6.1永久代与元空间

早在JDK1.8以前,方法区使用的永久代的实现方式,而在1.8后才正式确定使用元空间。那么二者实现上有什么区别呢????

最大的区别就是前者是使用的虚拟机内存,后者使用了直接内存,也就是说永久代的内存大小受JVM限制,而元空间内存大小受真实机子内存大小限制,明显后者内存大小更大,前者更容易OOM。

在方法区使用元空间后,字符串常量池也从方法区移动到了堆内存中。

6.2运行时常量池

提到方法区,就不得不提到一个叫运行时常量池的东西,它也是方法区的一部分。

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

7.堆

堆内存是整个虚拟机中最大的一块,这块区域是被线程共享的,这块对象就是用来在程序执行时,大部分对象存放的地方(还有极小一部分可能会发生逃逸分析,在栈上创建和销毁)。

堆这块区域,也是最容易发生OOM的地方,原因可想而知,公共的地方,大家都来这里放东西,时间一长,没有空间也很正常,所以这块区域也是发生GC(Garbage Collected)频率最高的一个场所。(具体GC流程,下篇文章会详细介绍)

7.1 对象创建

堆中的对象(普通对象)创建过程也是比较讲究的,下面我们带着问题,一步一步理解这个过程

首先,如何创建?

很简单的,new关键字

那么问题又来了,对象创建依赖的信息从哪里来?

当Java遇到一条字节码new指令的时候,首先将去检测这个指令能否在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那么必须先执行相应的类加载(双亲委派模型)。

对象依赖信息得到后,内存大小该如何划分分配?

当一个类被加载之后,相应的对象创建所需的内存大小也就能被确定了。那么要在堆中创建对象,就需要划分空间,JVM中有两种划分空间的方式,分别是“指针碰撞”和“空闲列表”

  • 指针碰撞
    假设Java堆中内存分配绝对规整,使用过的和未使用的分成两边,只需要在边界设置指针,这个指针只需要挪动和对象大小一样的距离即可,这种就是指针碰撞
  • 空闲列表
    假如Java堆内存并不规整,使用过的和未使用的都混在一起,这种情况,要分配内存就只能维护一个列表,这个列表记录了哪些内存可以使用,分配内存就需要在表中查找到足够到的区间进行分配即可,这就是空闲列表

并发下,对象创建的内存分配安全如何得到保证?

为我们所知的,堆内存是一个线程共享的,这就意味着,我们堆在划分内存大小的时候,可能会出现线程安全问题。可能出现线程1在给A分配大小的时候,还没来得及修改指针,但是线程2在创建B时,使用了这个指针,就导致了内存数据被改写了。解决这个问题有两个方式

  • 加锁同步
    实际实现中虚拟机是采用CAS+失败重试的方式保证更新操作的原子性
  • TLAB(Thread Local Allocation Buffer,本地缓冲区),也和ThreadLocal一样,给每个线程各自划分好区域,线程要创建对象,就在这个区域内创建就行,如果TLAB使用完了才需要进行同步锁定分配对象。如果JVM要使用TLAB,可以通过-XX:+/-UseTLAB参数来设定

实际上,内存分配成功之后,虚拟机还会对分配到的内存空间(不包括对象头)进行初始化工作,零值处理。这步操作是为了保证对象实例字段在Java代码中可以不赋值就能直接使用。

经历以上步骤,对象创建后,对象还需要设置什么?

需要设置“对象属于哪个类的实例”、“类的元数据信息“”、“对象hash码(实际调用Object::hashCode才会生成)”、“GC分代年龄”,这些信息都被描述在对象头中

最终

在上面工作都完成后,看似一个对象已经被创建了,但实际上,整个生命过程还差一步,即初始化,构造函数中的初始化工作还没有被真正执行,也就是 < init > ()方法,所以值都是默认为零值的,所以当构造函数执行完成后,一个对象就被完成创建了。

7.2 对象的内存布局

在了解一个对象的创建过程后,我们来看看,一个对象内部布局是如何的,直接看下图:
在这里插入图片描述
对象头:这部分包含了两部分信息

  • 第一部分:HashCode、GC分代年龄、锁状态标记、线程持有的锁、偏向锁ID、偏向锁时间戳等信息等,这部分信息官方称之为:Mark Word,这部分数据在32位和64位虚拟机(未开启指针压缩)中分别占用32bit和64bit。
  • 第二部分:类型指针,即对象指向它的类型元数据的指针,Java虚拟机通过这个指针来确定这个对象是哪个类的实例。如果是数组对象,对象头中还会记录数组长度,如果不是则无记录。

实例数据:这部分数据是对象真正存储的有效信息

对齐填充:这部分的内容不是必然存在的,也没有特殊含义,这部分的主要作用就是保证这个对象大小是8字节的整数倍,差多少,尽可能补多少。

JVM执行流程

在这里插入图片描述

  • 代码编译:Java源代码通过Java编译器(javac)编译成字节码文件(.class文件)。

  • 类加载:JVM的类加载器将字节码文件加载到内存中,并进行校验、准备、解析等处理。

  • 内存分配:JVM为加载的类分配内存,包括方法区、堆、栈等。

  • 初始化:JVM对类进行初始化,包括静态变量的赋值、静态代码块的执行等。

  • 执行:JVM开始执行字节码指令,逐行读取字节码文件并执行。这个执行过程交给执行引擎将字节码翻译成CPU指令交给操作系统去执行

END
以上是本文全部内容,希望对你有所帮助

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

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

相关文章

扬帆起航:许战海方法论日文版正式发布

近日&#xff0c;中国头部战略咨询机构‘许战海咨询’最新研究成果《中国汽车行业新能源转型战略》行业白皮书日文版&#xff0c;即将在日本发布。同时发布的日文版核心方法论白皮书还有《主品牌进化战略》、《第二招牌增长战略》、《链主品牌&#xff1a;制造业的竞争之王》等…

关于代理服务器那点事

问题&#xff1a;我们有时候会遇到&#xff0c;后端再本地服务器开发&#xff0c;不是测试服上&#xff0c;这时候我们就需要代码几个baseurl 答&#xff1a;一个axios只能代理一个baseurl axios 库本身只能代理一个 baseURL。在一个 axios 实例中&#xff0c;只能指定一个基…

Spring framework day 01:spring 整合数据源(连接池)

前言 在现代的企业应用开发中&#xff0c;数据库是不可或缺的一部分。而对于大部分的应用程序来说&#xff0c;与数据库的交互涉及到频繁的连接、查询和事务操作。为了提高应用程序的性能和可扩展性&#xff0c;使用连接池来管理数据库连接是一个不错的选择。而 Spring 框架提…

Spring事件ApplicationEvent源码浅读

文章目录 demo应用实现基于注解事件过滤异步事件监听 源码解读总结 ApplicationContext 中的事件处理是通过 ApplicationEvent 类和 ApplicationListener 接口提供的。如果将实现了 ApplicationListener 接口的 bean 部署到容器中&#xff0c;则每次将 ApplicationEvent 发布到…

为什么Redis集群的最大槽数是16384个?

对于客户端请求的key&#xff0c;根据公式HASH_SLOTCRC16(key) mod 16384&#xff0c;计算出映射到哪个分片上&#xff0c;然后Redis会去相应的节点进行操作&#xff01; 为什么有16384个槽&#xff1f; Redis集群并没有使用一致性hash而是引入了哈希槽的概念。Redis 集群有1…

线性代数-Python-01:向量的基本运算 -手写Vector -学习numpy的基本用法

文章目录 代码目录结构Vector.py_globals.pymain_vector.pymain_numpy_vector.py 一、创建属于自己的向量1.1 在控制台测试__repr__和__str__方法1.2 创建实例测试代码 二、向量的基本运算2.1 加法2.2 数量乘法2.3 向量运算的基本性质2.4 零向量2.5 向量的长度2.6 单位向量2.7 …

C++string类重要函数模拟实现

为了和C标准库区分&#xff0c;以下代码除主函数外均在namespace空间 目录 一.成员 二、带参构造函数 三、拷贝构造函数和赋值运算符重载 四、析构函数 五、重要成员函数实现 1. c_str函数 2. operator[]重载 3. size函数和capacity函数 4.reverse函数 5. push_back和…

【解决】运行vue项目,启动报错 in ./node_modules/@intlify/core-base/dist/core-base.cjs

我的处理方式: 一开始查了好多方法&#xff0c;删除node_modules&#xff0c;重新安装&#xff0c;切换node版本等&#xff0c;但是发现并没有用 之后来发现是安装依赖包的时候有些包安装失败导致的&#xff0c;只要有针对性的重新安装依赖就可以了 例如&#xff1a; in ./n…

Splashtop 与 Canopy 携手共同增强对物联网设备的远程管理

2023年10月17日 加利福尼亚州库比蒂诺 Splashtop 在安全远程访问解决方案领域处于领先地位&#xff0c;Canopy 则是用于复杂硬件部署的领先 RMM 平台&#xff0c;双方今天宣布达成战略合作伙伴关系&#xff0c;以进一步增强和简化对物联网设备的远程管理。通过此次合作&#x…

JSX的本质

一、本质 React.createElement即h函数&#xff0c;返回vnode第一个参数&#xff0c;可能是组件&#xff0c;也可能是html tag组件名&#xff0c;首字母必须大写&#xff08;React规定&#xff09; 二、babel试一试 &#xff08;babel集成了jsx的编译环境&#xff09; // JSX…

Linux生产者消费者模型

生产者消费者模型 生产者消费者模型生产者消费者模型的概念生产者消费者模型的特点生产者消费者模型优点 基于BlockingQueue的生产者消费者模型基于阻塞队列的生产者消费者模型模拟实现基于阻塞队列的生产消费模型 生产者消费者模型 生产者消费者模型的概念 生产者消费者模式就…

Spring framework Day 23:容器事件

前言 容器事件是 Spring Framework 中的一个重要概念&#xff0c;它提供了一种机制&#xff0c;使我们能够更好地了解和响应 Spring 容器中发生的各种事件。通过容器事件&#xff0c;我们可以在特定的时间点监听和处理容器中的各种状态变化、操作和事件触发&#xff0c;以实现…

Go 存储系列:LSM存储引擎 LevelDB

概念介绍 LSM-Tree 被是一种面向写多读少应用场景的数据结构 &#xff0c;被 Hbase、RocksDB 等强力 NoSQL 数据库采用作为底层文件组织方式。 简单的LSM-Tree 包含 2 层树状数据结构&#xff1a; Memtable 并完全驻留在内存中&#xff08;假设 T0&#xff09; SStables 存储…

Unity SRP 管线【第二讲:Draw Call】

参考&#xff1a; https://edu.uwa4d.com/lesson-detail/282/1309/0?isPreview0 文章目录 参考&#xff1a;一、Shader1.HLSL引入2.获取Unity提供的标准输入3.Unity提供的运算库SpaceTransform库的宏对应补充&#xff1a; 4.标准库Common.hlsl5.SpaceTransforms库引入Commo…

【笔记】Endnote20插入文献

方法一 1.首先选中要参考的文章 2.在word里选好格式 3.在word里点击插入已选文献 前提&#xff1a;已经将光标放在要插入的位置了 4.插入文献即可&#xff0c;效果如下 方法二&#xff08;方便些&#xff0c;但是word容易闪退&#xff09; 1.点击要插入的文献&#xff0c;…

Apache Log4j Server (CVE-2017-5645) 反序列化命令执行漏洞

文章目录 Apache Log4j Server 反序列化命令执行漏洞&#xff08;CVE-2017-5645&#xff09;1.1 漏洞描述1.2 漏洞复现1.2.1 环境启动1.2.2 漏洞验证1.2.3 漏洞利用 1.3 加固建议 Apache Log4j Server 反序列化命令执行漏洞&#xff08;CVE-2017-5645&#xff09; 1.1 漏洞描述…

蓝绿发布,灰度发布,滚动发布

写在前面 本文看下生产环境中有哪些常用的发布策略。 1:蓝绿发布 蓝绿发布要求将线上机器分成逻辑上的AB两&#xff08;蓝绿就是两种颜色&#xff09;组&#xff0c;升级时先将A组从负载均衡中摘除&#xff0c;由B组对外提供服务&#xff0c;如下图&#xff1a; 当A组升级…

嵌入式学习笔记(59)内存管理之结构体

数据结构&#xff1a;是一门研究数据在内存中如何分布的学问。 1.5.1.最简单的数据结构&#xff1a;数组 数组的特点&#xff1a;类型相同、意义相关 数组的优势&#xff1a;数组比较简单&#xff0c;访问使用下标&#xff0c;可以随机访问&#xff08;就是可以通过下标随机…

【迎战2023双十一】小白也能玩转!手把手教你实时获取多平台店铺数据,轻松实现数据大屏展示

要实时获取多平台店铺数据进行数据大屏展示&#xff0c;需要进行以下步骤&#xff1a; 确定数据采集方式&#xff1a;通过爬虫程序&#xff08;如Python的BeautifulSoup、Scrapy等爬虫框架&#xff09;或API接口来实现数据的获取&#xff0c;确定该方法所需的数据格式和调用方…

字节码增强技术-ASM

概述 在Java中一般是用javac命令编译源代码为字节码文件&#xff0c;一个.java文件从编译到运行的示例如图所示: 使用字节码的好处&#xff1a;一处编译&#xff0c;到处运行。java 就是典型的使用字节码作为中间语言&#xff0c;在一个地方编译了源码&#xff0c;拿着.class …