JVM专题——内存结构

news2024/7/6 20:23:56

本文部分内容节选自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而言, 主要使用第二种方式进行对象访问

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

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

相关文章

Neo4j数据库(一)

目录 新建节点 Neo4j CQL创建具有属性的节点 多个标签到节点 单个标签到关系 MATCH命令 RETURN命令&#xff1a; Neo4j CQL - MATCH & RETURN匹配和返回 总结&#xff1a;本文介绍了Neo4j的CREATE&#xff0c;MATCH&#xff0c;RETURN的基本操作 新建节点 Neo4j创建一…

Coursera上托福专项课程02:TOEFL Speaking and Writing Sections Skills Mastery 学习笔记

TOEFL Speaking and Writing Sections Skills Mastery Course Certificate 本文是学习 https://www.coursera.org/learn/toefl-speaking-writing-sections-skills-mastery 这门课的学习笔记&#xff0c;如有侵权&#xff0c;请联系删除。 文章目录 TOEFL Speaking and Writing…

Unity 代码控制播放序列帧动画的实现

在Unity中有些应用场景的动画是通过序列帧实现的。 如下图即为一个英雄攻击的一个动画&#xff1a; 那么如何实现该序列帧动画的播放呢&#xff0c;我们可以通过代码来控制播放。 1、把以上序列帧导入编辑器中&#xff0c;并修改图片属性&#xff0c;如下图所示&#xff0c;其…

三菱上升沿和下降沿

1&#xff0c;上升沿 含义 上升沿是在接通的第一个周期执行。 2&#xff0c;下将沿 断开的第一个周期执行 M0 按下后Y0 亮 M1 松开后Y0灭

Docker 安装 Linux 系统可视化监控 Netdata

docker 安装 netdata 前提准备Docker 两种方式部署 Netdata1、使用 docker run 命令运行 netdata 服务2、使用 docker compose 运行 netdata 服务 Netdata 服务可视化界面Netdata 汉化处理 前提准备 说明&#xff1a;此处使用 windows11 安装的 docker desktop & wsl2/apli…

2. 如何让mybatis-plus的逻辑删除注解@TableLogic临时失效

文章目录 如何让mybatis-plus的逻辑删除注解TableLogic临时失效1. 场景复现1.1 controller代码1.2 service层代码1.3 entity代码 2. 问题分析3. 解决方案3.1 说明3.2 核心代码3.3 service方法对应修改为3.4 运行结果 如何让mybatis-plus的逻辑删除注解TableLogic临时失效 1. 场…

python-基础篇-字符串、列表、元祖、字典-列表

文章目录 2.3.2列表2.3.2.1列表介绍2.3.2.1.1列表的格式2.3.2.1.2打印列表 2.3.2.2列表的增删改查2.3.2.2.1列表的遍历2.3.2.2.1.1使用for循环2.3.2.2.1.2使用while循环 2.3.2.2.2添加元素("增"append, extend, insert)2.3.2.2.2.1append 2.3.2.2.2.2extend2.3.2.2.2…

路由策略与路由控制之双点双向重发布(OSPF-ISIS)实验

双点双向重发布在路由协议中&#xff0c;特别是在OSPF&#xff08;开放式最短路径优先&#xff09;与IS-IS&#xff08;中间系统到中间系统&#xff09;等协议之间&#xff0c;指的是在两个协议间或者两个进程间进行路由信息共享的机制。这种机制涉及到在两个不同的协议区域使用…

回归预测 | Matlab基于CPO-GPR基于冠豪猪算法优化高斯过程回归的多输入单输出回归预测

回归预测 | Matlab基于CPO-GPR基于冠豪猪算法优化高斯过程回归的多输入单输出回归预测 目录 回归预测 | Matlab基于CPO-GPR基于冠豪猪算法优化高斯过程回归的多输入单输出回归预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 Matlab基于CPO-GPR基于冠豪猪算法优化高斯…

java算法day45 | 动态规划part07 ● 70. 爬楼梯 (进阶) ● 322. 零钱兑换 ● 279.完全平方数

70. 爬楼梯 &#xff08;进阶&#xff09; 题目描述&#xff1a; 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬至多m (1 < m < n)个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 注意&#xff1a;给定 n 是一个正整数。 输入描述&#xff1a;输入…

助力智能密集人群检测计数与拥挤预警分析,基于YOLOv8全系列参数模型【n/s/m/l/x】开发构建通用场景下人群检测计数与拥挤分析识别系统

在一些人流量比较大的场合&#xff0c;或者是一些特殊时刻、时段、节假日等特殊时期下&#xff0c;密切关注当前系统所承载的人流量是十分必要的&#xff0c;对于超出系统负荷容量的情况做到及时预警对于管理团队来说是保障人员安全的重要手段&#xff0c;本文的主要目的是想要…

Java 学习和实践笔记(51):二分法查找(折半检索)

二分法查找&#xff08;折半检索&#xff09;又叫binary search. 要在一堆数据中查找是否存在某一个已知数&#xff0c;二分法查找的步骤&#xff1a; 第一步&#xff0c;对数据实现排序 第二步&#xff0c;将该数与排序后的数据集的中间一个数进行比较 第三步&#xff0c;…

设计模式总结-原型设计模式

原型设计模式 模式动机模式定义模式结构模式分析深拷贝和浅拷贝原型模式实例与解析实例一&#xff1a;邮件复制&#xff08;浅克隆&#xff09;实例二&#xff1a;邮件复制&#xff08;深克隆&#xff09; 模式动机 在面向对象系统中&#xff0c;使用原型模式来复制一个对象自…

动态属性的响应式问题和行内编辑的问题

动态属性的响应式问题 通过点击给目标添加动态数据&#xff0c;该数据不具备响应式特性 如下图&#xff1a; 点击编辑&#xff0c;前面的数据框会变成输入框&#xff0c;点取消会消失 // 获取数据 async getList () {const res await xxxthis.list res.data.rows// 1. 获…

2024 最新版 Proteus 8.17 安装汉化教程

前言 大家好&#xff0c;我是梁国庆。 今天给大家带来的是目前 Proteus 的最新版本——Proteus 8.17。 时间&#xff1a;2024年4月4日 获取 Proteus 安装包 我已将本篇所使用的安装包打包上传至百度云&#xff0c;扫描下方二维码关注「main工作室」&#xff0c;后台回复【…

阿里 Arthas 工具使用

Arthas 是一款线上监控诊断产品&#xff0c;通过全局视角实时查看应用 load、内存、gc、线程的状态信息&#xff0c;并能在不修改应用代码的情况下&#xff0c;对业务问题进行诊断&#xff0c;包括查看方法调用的出入参、异常&#xff0c;监测方法执行耗时&#xff0c;类加载信…

基于微信小程序的外卖管理系统的设计与实现(论文+源码)_kaic

摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。针对高校教师成果信息管理混乱&#xff0c;出错率高&#xff0c;信息安全…

Java:接口应用(Clonable 接口和深拷贝)

目录 1.引例2.Object中clone方法的实现3.Cloneable接口讲解4.深拷贝和浅拷贝4.1浅拷贝4.2深拷贝 1.引例 Java 中内置了一些很有用的接口, Clonable 就是其中之一. Object 类中存在一个 clone 方法, 调用这个方法可以创建一个对象的 “拷贝”. 但是要想合法调用 clone 方法。必…

吴恩达2022机器学习专项课程(一) 5.2 向量化(1) 5.3 向量化(2)

问题预览/关键词 什么是向量化&#xff1f;向量化的好处是&#xff1f;如何向量化多元线性回归函数的参数&#xff1f;如何在Python中向量化参数&#xff1f;计算机底层是如何计算向量化的&#xff1f;向量化示例 笔记 1.向量化 一种在数学和计算中广泛使用的概念&#xff…

vscode 连接远程服务器 服务器无法上网 离线配置

离线配置 vscode 连接远程服务器 .vscode-server 1. .vscode-server下载 使用vscode连接远程服务器时会自动下载配置.vscode-server文件夹&#xff0c;如果远程服务器无法联网&#xff0c;则需要手动下载 1&#xff09;网址&#xff1a;https://update.code.visualstudio.com…