JVM专题——内存结构

本文部分内容节选自Java Guide和《深入理解Java虚拟机》, Java Guide地址: https://javaguide.cn/java/jvm/memory-area.html

🚀 基础(上) → 🚀 基础(中) → 🚀基础(下) → 🤩集合(上) → 🤩集合(下) → 🤗JVM专题1 → 🤗JVM专题2 → 🤗JVM专题3

运行时数据区域

JVM 在执行 Java 程序的过程中会把它所管理的内存划分为若干个不同的数据区域. 这些区域有各自的用途, 以及创建和销毁的时间, 有的区域随着虚拟机进程的启动而一直存在, 有些区域则是依赖于用户线程的启动和结束而建立和销毁. 根据 Java 虚拟机规范 的规定, JVM 所管理的内存将会包括以下几个运行时区域

Java运行时数据区域(JDK1.7)
Java运行时数据区域(JDK1.8)
线程私有的 :

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

线程共享的 :

  • 方法区
  • 直接内存(非运行时数据区的一部分)

程序计数器

程序计数器是一块较小的内存空间, 它可以看作是当前线程所执行的字节码的行号指示器. 字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令, 它是程序控制流的指示器, 分支, 循环, 跳转, 异常处理, 线程恢复都需要依赖这个计数器来完成

由于 JVM 的多线程是通过线程轮流切换, 分配处理器执行实际的方式来实现的, 在任何一个确定的时刻, 一个处理器都只会执行一条线程中的指令. 因此, 为了线程切换之后能恢复到正确的执行位置, 每条线程都需要有一个独立的程序计数器, 各条线程之间计数器互不影响, 独立存储, 所以这类内存区域为线程私有的内存

注意: 此内存区域是唯一一个在 Java虚拟机规范 中没有规定任何 OutOfMemoryError 情况的区域

虚拟机栈

和程序计数器一样, 虚拟机栈也是线程私有的, 它的生命周期和线程相同. 虚拟机栈描述的是 Java 方法执行的线程内存模型: 每个方法被执行的时候, JVM 都会同步创建一个栈帧用于存储局部变量表, 操作数栈, 动态链接, 方法出口等信息. 每一个方法被调用到执行完毕的过程, 就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程

局部变量表存放了编译期可知的各种 JVM 基本数据类型(boolean, byte, char , short, int, float, long, double), 对象引用和returnAddress类型. 这些数据类型在局部变量表中的存储空间以局部变量槽来表示, 其中64位的数据会占据两个槽, 其余的只占据一个. 局部变量表所需的内存空间在编译期间就完成分配, 当进入一个方法时, 这个方法需要在栈帧中分配多大的局部变量空间是完全确定的, 在方法运行期间不会改变局部变量表的大小

操作数栈主要用于方法调用的中转站, 用于存放方法执行过程中产生的中间计算结果. 此外, 计算过程中产生的临时变量也会放在操作数栈中

动态链接主要服务一个方法需要调用其他方法的场景. Class 文件的常量池中保存有大量的符号引用比如方法引用的符号引用. 当一个方法要调用其他方法, 需要将常量池中指向方法的符号引用转化为其在内存地址中的直接引用. 动态链接的作用就是为了将符号引用转换为调用方法的直接引用, 这个过程也被叫做动态连接

栈空间不是无限的, 如果函数调用陷入无限循环, 就会导致栈压入过多栈帧, 导致栈空间过深. 当线程请求栈的深度超过 JVM 虚拟机栈的最大深度时, 就会抛出 StackOverflowError

栈帧随着方法调用而创建, 随着方法结束而销毁, 无论是正常完成还是异常完成都算方法结束

除了 StackOverflowError , 栈还可能出现 OutOfMemoryError 错误, 这是因为如果栈的内存大小可以动态扩展, 虚拟机在动态扩展栈时无法申请到足够的内存空间, 则会抛出 OutOfMemoryError

本地方法栈

本地方法栈和虚拟机栈非常相似, 差别仅在于虚拟机栈为虚拟机执行 Java 方法服务, 本地方法栈则为虚拟机使用到的本地方法服务

和虚拟机栈一样, 本地方法栈也会抛出 StackOverflowErrorOutOfMemoryError

堆是 JVM 所管理的内存中最大的一块, Java 堆是所有线程共享的一块内存区域, 在虚拟机启动时创建. 该内存区域的唯一目的就是对象实例, Java中几乎所有的对象实例都在这里分配内存.

Java 堆是垃圾收集器管理的内存区域, 因此也被称为 “GC堆”. 从回收内存的角度看, 由于现代垃圾收集器大部分都是基于分代收集理论设计的, 所以 Java 堆还可以细分为: 新生代和老年代, 再细致一点: Eden, Survivor, Old空间. 进一步划分的目的是为了更好的回收内存或更快的分配内存

在JDK7及以前的版本, 堆分为下面三个部分:

  1. 新生代
  2. 老生代
  3. 永久代

JDK8 之后永久代已被元空间取代, 元空间使用的是本地内存

在这里插入图片描述

大部分情况下, 对象首先在 Eden区分配, 在一次新生代垃圾回收之后, 如果对象还存活, 则会进入 S0或S1 , 并且对象的年龄会+1, 当年了增加到一定程度(默认为15岁), 就会晋升到老年代.

堆中最经常出现的就是 OutOfMemoryError 错误, 并且这种错误的表现方式有几种:

  1. java.lang.OutOfMemoryError: GC Overhead Limit Exceeded : 当JVM 花太多时间执行垃圾回收且只能回收很少的堆空间时, 就会发生此错误
  2. java.lang.OutOfMemoryError: Java Heap Space : 假如创建新对象时, 堆内存中的空间不足以存放新创建的对象, 就会引发此错误

方法区

方法区和堆一样, 是线程共享的内存区域, 它用于存储已被虚拟机加载的类型信息, 常量, 静态变量, 即时编译器编译后的代码缓存等数据

方法区和永久代, 元空间是什么关系? 方法区和永久代, 元空间类似于Java中接口和类的关系, 这里类可以看作是永久代和元空间, 接口可以看作是方法区, 也就是说永久代和元空间其实是方法区的两种实现. 永久代是JDK1.8之前对方法区的实现, 元空间是JDK1.8之后对方法区的实现

为什么要把永久代替换成元空间?

  1. 整个永久代有 JVM 本身设置的固定大小上限, 无法进行调整; 而元空间使用的是本地内存, 受本机可用内存的限制, 虽然元空间可能会溢出,但比原来出现溢出的概率要小
  2. 元空间中存放的是类的元数据, 这样加载多少类的元数据就不由 MaxPermSize 控制了, 而由系统的实际可用空间控制, 这样能加载的类就更多了
  3. 在 JDK8, 合并 HotSpot 和 JRockit 的代码时, JRockit 从来没有一个叫做永久代的地方, 合并之后也没必要额外设置一个永久代
  4. 永久代会为GC带来不必要的复杂度, 且回收效率偏低

根据 Java虚拟机规范 , 如果方法区无法满足新的内存分配需求, 将抛出 OutOfMemoryError 异常

运行时常量池

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

运行时常量池相较于 Class 文件常量池的另外一个重要特征就是具备动态性, Java语言并不要求常量一定只有编译期才能产生, 也就是说, 并非预置入Class文件中常量池的内容才能进入方法去运行时常量池, 运行期间也可以将新的常量放入池中

当常量池无法再申请到内存时就会抛出 OutOfMemoryError 异常

字符串常量池

字符串常量池是 JVM 为了提高性能和减少内存消耗针对字符串专门开辟的一块区域, 主要目的是为了避免字符串的重复创建

JDK1.7之前, 字符串常量池存放在永久代, JDK1.7后字符串常量池移动到堆

为什么要把字符串常量池移动到堆?

主要是因为永久代的GC效率太低, 只有整堆收集(Full GC)的时候才会执行GC. Java程序在通常有大量的被创建的字符串等待回收, 将字符串常量池放到堆中, 可以更高效地回收字符串内存

直接内存

直接内存是特殊的内存缓冲区, 它并不在 Java 堆或方法区分配, 而是通过 JNI 的方式在本地内存上分配

直接内存并不是虚拟机运行时数据区的一部分, 也不是虚拟机规范中定义的内存区域, 但是这部分内存也被频繁地使用, 而且也可能导致 OutOfMemoryError 错误出现

直接内存分配不会受到 Java 堆的限制, 但是会受到本机内存大小和处理器寻址空间的限制

HotSpot虚拟机对象探秘

对象创建

Step1: 类加载检查

当虚拟机碰到一条 new 指令时, 首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用, 并且检查这个符号引用代表的类是否已被加载过, 解析过和初始化过. 如果没有, 那必须先执行相应的类加载过程

Step2: 分配内存

在类加载检查通过后, 接下来虚拟机将为新生对象分配内存. 对象所需的内存大小在类加载完成后便可确定, 为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来. 分配方式有 “指针碰撞” 和 “空闲列表”, 取决哪一种主要由 Java 堆是否规整决定

  • 指针碰撞
    • 适用场合: 堆内存规整的情况
    • 原理: 用过的内存全部整合到一边, 没有用过的内存放在另一边, 中间有一个分界指针, 只需要向着没用过的内存方向将该指针移动对象内存大小位置即可
    • 使用该分配方式的GC收集器: Serial, ParNew
  • 空闲列表
    • 适用场合: 堆内存不规整的情况
    • 原理: 虚拟机会维护一个列表, 该列表中会记录哪些内存块是可用的, 在分配的时候, 找一块足够大的内存划分给对象实例, 最后更新列表记录
    • 使用该分配方式的GC收集器: CMS

内存分配涉及到的并发问题

通常来讲, 虚拟机通过以下两种方式保证线程安全

  • CAS+失败重试 : 虚拟机采用CAS配上失败重试的方式保证更新操作的原子性
  • TLAB : 为每一个线程预先在Eden区分配一块内存, JVM在给线程中的对象分配内存时, 首先在TLAB分配, 当对象大于TLAB中的剩余内存或TLAB的内存已用尽时, 再采用上述的CAS进行内存分配

Step3: 初始化零值

内存分配完成时, 虚拟机需要将分配到的内存空间都初始化为零值, 这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用, 程序能访问到这些字段的数据类型所对应的零值

Step4: 设置对象头

设置完零值之后, 虚拟机还要对对象进行必要的设置, 例如这个对象是哪个类的实例, 如何才能找到类的元数据信息, 对象的哈希码, 对象的 GC分代年龄等信息, 这些信息存储在对象的对象头之中

Step5: 执行init方法

执行new指令后还要接着执行 <init> 方法, 这样一个真正可用的对象才算完全产生出来

对象的内存布局

在HotSpot虚拟机中, 对象在内存中的布局可以分为3块区域: 对象头 , 实例数据 , 对齐填充

HotSpot虚拟机的对象头包含两部分信息, 第一部分用于存储对象自身的运行时数据, 另一部分是类型指针

实例数据部分是对象真正存储的有效信息, 也是程序中所定义的各种类型的字段内容

对齐填充部分不是必然存在的, 也没有什么特殊的含义, 仅仅起占位左右, 由于HotSpot的内存管理系统要求对象起始地址必须是8字节的整数倍, 换句话说就是任何对象的大小都必须是8字节的整数倍. 对象头部分已经被设计成8字节的倍数, 所以如果对象实例数据部分没有对齐的话, 就需要对齐填充部分来补全

对象的访问定位

对象访问方式主要有使用句柄直接指针两种

  • 如果使用句柄访问的话, Java堆中可能会划分出一块内存作为句柄池, reference中存储的就是对象的句柄地址, 而句柄中包含对象实例数据与类型数据各自具体的地址信息

在这里插入图片描述

  • 如果使用直接指针访问的话, Java堆中对象的内存布局就必须考虑如何放置访问类型数据的相关信息, reference中存储的直接就是对象地址, 如果只是访问对象本身的话, 就不需要多一次间接访问的开销

在这里插入图片描述
使用指针访问的最大好处就是速度更快, 节省了一次指针定位的时间开销. 对于HotSpot而言, 主要使用第二种方式进行对象访问

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

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

相关文章

Day82:服务攻防-开发组件安全Solr搜索Shiro身份Log4j日志本地CVE环境复现

目录 J2EE-组件Solr-本地demo&CVE 命令执行&#xff08;CVE-2019-17558&#xff09; 远程命令执行漏洞(CVE-2019-0193) Apache Solr 文件读取&SSRF (CVE-2021-27905) J2EE-组件Shiro-本地demo&CVE CVE_2016_4437 Shiro-550Shiro-721(RCE) CVE-2020-11989(身…

《Java面试自救指南》(专题二)计算机网络

文章目录 力推的计网神课get请求和post请求的区别在浏览器网址输入一个url后直到浏览器显示页面的过程常用状态码session 和 cookie的区别TCP的三次握手和四次挥手七层OSI模型&#xff08;TCP/IP协议模型&#xff09;各种io模型的知识http协议和tcp协议的区别https和http的区别…

软件杯 深度学习乳腺癌分类

文章目录 1 前言2 前言3 数据集3.1 良性样本3.2 病变样本 4 开发环境5 代码实现5.1 实现流程5.2 部分代码实现5.2.1 导入库5.2.2 图像加载5.2.3 标记5.2.4 分组5.2.5 构建模型训练 6 分析指标6.1 精度&#xff0c;召回率和F1度量6.2 混淆矩阵 7 结果和结论8 最后 1 前言 &…

【正点原子探索者STM32F4】TFTLCD实验学习记录:FSMC控制 TFTLCD的寄存器配置

FSMC控制 TFTLCD的寄存器配置 异步模式 A控制 TFTLCDFSMC寄存器配置ILI9341电平持续时间要求 参考 异步模式 A控制 TFTLCD LCD以ILI9341为例 FSMC寄存器配置 对于异步突发访问方式&#xff0c; FSMC 主要设置 3 个时间参数&#xff1a;地址建立时间(ADDSET)、 数据 建立时间…

docker的安装及入门指令

目录 一、将docker安装到云服务器步骤 1.更新系统yum版本 2.安装所需依赖 3.添加docker仓库设置(使用的是阿里云) 4.安装docker引擎 5.启动docker并开启自动启动 6. 检查是否安装成功&#xff0c;成功会显示相应版本&#xff0c;否则安装失败 二、docker常用命令 1.从…

使用LIO-SAM进行点云赋色 与 激光雷达和相机的精细化标定(防止自己忘记的博客)----- 激光雷达和相机的精细化标定

目录 1 标定相机 2 激光雷达、相机粗标定 3 精细化标定激光雷达和相机 1 标定相机 使用Kaliber标定D435i相机&#xff0c;本次标定的分辨率为1920*1080&#xff0c;相机的内参如下&#xff1a; FX&#xff1a;1439.96402547 FY&#xff1a;1442.82612329 CX&#xff1a;979.0…

Hadoop-MapReduce

一、MapReduce 概述 1.1 MapReduce 定义 MapReduce 是一个分布式运算程序的编程框架&#xff0c;是用户开发“基于 Hadoop 的数据分析应用”的核心框架。 MapReduce 核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序&#xff0c;并发运行在…

需求分析及设计定义

背景 经过不断的折腾&#xff0c;一切过程都是为了呈现输出&#xff0c;这个阶段就是要交付需求和方案的环节了&#xff0c;很多失败的项目就是上来就到这个环节&#xff0c;倒着捣鼓&#xff0c;先写个文档&#xff0c;做个原型&#xff0c;甚至提出方案&#xff0c;然后再和…

KeyguardClockSwitch的父类

KeyguardClockSwitch 定义在KeyguardStatusView中, mClockView findViewById(R.id.keyguard_clock_container);KeyguardClockSwitch的父类为&#xff1a; Class Name: LinearLayout Class Name: KeyguardStatusView Class Name: NotificationPanelView Class Name: Notificat…

设计模式总结-简单工厂模式

简单工厂模式 创建型模式创建型模式概述创建型模式种类 简单工厂模式模式定义模式动机模式结构模式分析模式实例与解析实例一&#xff1a;简单电视机工厂实例二&#xff1a;权限管理 模式优缺点简单工厂模式的优点简单工厂模式的缺点 模式适用环境模式扩展 小结 创建型模式 创…

redis进阶入门主从复制与哨兵集群

一、主从复制 1.1背景 一般来说&#xff0c;要将 Redis用于工程项目中&#xff0c;只使用一台 Redist是万万不能的&#xff0c;原因如下&#xff1a; 从结构上&#xff0c;单个 Redist服务器会发生单点故障&#xff0c;井且一台服务器需要处理所有的请求负載&#xff0c;压力…

ABBYY FineReader15免费电脑OCR图片文字识别软件

产品介绍&#xff1a;ABBYY FineReader 15 OCR图片文字识别软件 ABBYY FineReader 15是一款光学字符识别&#xff08;OCR&#xff09;软件&#xff0c;专门设计用于将扫描的文档、图像和照片中的文本转换成可编辑和可搜索的格式。这款软件利用先进的OCR技术&#xff0c;能够识别…

使用 LLMLingua-2 压缩 GPT-4 和 Claude 提示

原文地址&#xff1a;Compress GPT-4 and Claude prompts with LLMLingua-2 2024 年 4 月 1 日 向大型语言模型&#xff08;LLM&#xff09;发送的提示长度越短&#xff0c;推理速度就会越快&#xff0c;成本也会越低。因此&#xff0c;提示压缩已经成为LLM研究的热门领域。 …

【opencv】教程代码 —ml (主成分分析、支持向量机、非线性支持向量机)

1. introduction_to_pca.cpp 主成分分析 /*** file introduction_to_pca.cpp* brief 这个程序演示了如何使用OpenCV PCA 提取物体的方向* author OpenCV团队*/// 包含OpenCV函数库所需要的头文件 #include "opencv2/core.hpp" #include "opencv2/imgproc.hpp&q…

人工智能数据分析Python常用库 04 matplotlib库

文章目录 一、matplotlib库的作用与环境配置1、环境配置示例2、改变绘图风格3、保存图片 二、绘制二维图形1、折线图&#xff08;1&#xff09;示例&#xff08;2&#xff09;调整线条颜色&#xff1a;&#xff08;3&#xff09;调整线条风格&#xff08;4&#xff09;调整线宽…

深入浅出 -- 系统架构之分布式CAP理论和BASE理论

科技进步离不开理论支撑&#xff0c;而当下大行其道的分布式架构&#xff0c;透过繁荣昌盛表象&#xff0c;底层同样离不开诸多分布式理论撑持。当然&#xff0c;相信诸位在学习分布式相关技术时&#xff0c;必然学到过两个分布式领域中的基础理论&#xff0c;即&#xff1a;CA…

设置Chrome打开链接在新标签页显示

Chrome版本 版本 123.0.6312.106&#xff08;正式版本&#xff09; &#xff08;64 位&#xff09; 下面这两个页面都有设置按钮&#xff1a; https://www.google.com/?pli1或者https://www.google.com/?hlzh-CN 要先退出账号&#xff0c;要不然看不到右下角的 “设置” 。…

SpamSieve mac垃圾邮件过滤器 直装激活版

SpamSieve通过强大的垃圾邮件过滤技术&#xff0c;帮助用户有效管理和消除不想要的电子邮件。它能与多种电子邮件客户端无缝集成&#xff0c;如Apple Mail、Microsoft Outlook、Airmail等。 软件下载&#xff1a;SpamSieve mac直装激活版下载 该软件利用先进的算法和机器学习技…

Vue知识点(学习笔记)

Vue知识点学习 一、Vue快速上手1. 脚本引用 二、Vue小知识1. 简写v-bindv-modelel和data的两种写法&#xff1a;eldata 2. MVVM模型3. Object.defineproperty方法4. Vue的数据代理5. 事件处理6. 阻止默认事件7. Vue中事件修饰符&#xff1a;8. 引入Element-Ui 一、Vue快速上手 …

虚幻UE5数字孪生蓝图开发教程

一、背景 这几年&#xff0c;智慧城市/智慧交通/智慧水利等飞速发展&#xff0c;骑士特意为大家做了一个这块的学习路线。 二、这是学习大纲 1.给虚幻UE5初学者准备的智慧城市/数字孪生蓝图开发教程 https://www.bilibili.com/video/BV1894y1u78G 2.UE5数字孪生蓝图开发教学…