【JVM】内存结构

内存结构

Java 虚拟机定义了若干种程序运行期间会使用到的运行时数据区,其中有一些会随着虚拟机启动而创建,随着虚拟机退出而销毁。另外一些则是与线程一一对应的,这些与线程一一对应的数据区域会随着线程开始和结束而创建和销毁。

  • 线程私有:程序计数器、虚拟机栈、本地方法区
  • 线程共享:堆、方法区, 堆外内存(Java7的永久代或JDK8的元空间、代码缓存)

image-20231017210357314

image-20231017210416444

程序计数器

可以看作是当前线程所执行的字节码的行号指示器,用来存储指向下一条指令的地址,即将要执行的指令代码。由执行引擎读取下一条指令。为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各线程之间计数器互不影响,独立存储,所以为线程私有。

两个作用:

  • 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
  • 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。

注意:程序计数器是唯一一个不会出现 OOM 的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。

虚拟机栈

主管 Java 程序的运行,它保存方法的局部变量、部分结果,并参与方法的调用和返回。每个线程在创建的时候都会创建一个虚拟机栈,其内部保存一个个的栈帧(Stack Frame),对应着一次次 Java 方法调用,是线程私有的,生命周期和线程一致。

特点
  1. 栈是一种快速有效的分配存储方式,访问速度仅次于程序计数器
  2. JVM 直接对虚拟机栈的操作只有两个:每个方法执行,伴随着入栈(进栈/压栈),方法执行结束出栈
  3. 栈不存在垃圾回收问题
  4. 可以通过参数-Xss来设置线程的最大栈空间,栈的大小直接决定了函数调用的最大可达深度
异常
  1. 如果采用固定大小的 Java 虚拟机栈,那每个线程的 Java 虚拟机栈容量可以在线程创建的时候独立选定。如果线程请求分配的栈容量超过 Java 虚拟机栈允许的最大容量,Java 虚拟机将会抛出一个 StackOverflowError 异常
  2. 如果 Java 虚拟机栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈,那 Java 虚拟机将会抛出一个OutOfMemoryError异常
栈帧的内部结构
  1. 局部变量表(Local Variables):主要存放了编译期可知的各种基本数据类型、对象引用类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)

  2. 操作数栈(Operand Stack)(或称为表达式栈) : 主要作为方法调用的中转站使用,用于存放方法执行过程中产生的中间计算结果。另外,计算过程中产生的临时变量也会放在操作数栈中

  3. 动态链接(Dynamic Linking):指向运行时常量池的方法引用, 主要服务一个方法需要调用其他方法的场景。

  4. 方法返回地址(Return Address):方法正常退出或异常退出的地址

    在这里插入图片描述

操作数栈的计算方式

例: 100 + 98
在这里插入图片描述

本地方法栈

  • 本地方法接口

一个 Native Method 就是一个 Java 调用非 Java 代码的接口。

  • 本地方法栈

Java 虚拟机栈用于管理 Java 方法的调用,而本地方法栈用于管理本地方法的调用

方法区

方法区(method area)只是 JVM 规范中定义的一个概念,用于存储类信息、常量池、静态变量、JIT编译后的代码等数据,并没有规定如何去实现它,不同的厂商有不同的实现。而永久代(PermGen)是Hotspot虚拟机特有的概念, Java8 的时候又被元空间取代了,永久代和元空间都可以理解为方法区的落地实现。

栈、堆、方法区的交互关系

在这里插入图片描述

永久代和元空间内存使用上的差异

Java虚拟机规范中只定义了方法区用于存储已被虚拟机加载的类信息、常量、静态变量和即时编译后的代码等数据

  1. jdk1.7开始,符号引用存储在native heap中,字符串常量和静态类型变量存储在普通的堆区中,但分离的并不彻底,此时永久代中还保存另一些与类的元数据无关的杂项
  2. jdk8后HotSpot 原永久代中存储的类的元数据将存储在metaspace中,而类的静态变量和字符串常量将放在Java堆中,metaspace是方法区的一种实现,只不过它使用的不是虚拟机内的内存,而是本地内存。在元空间中保存的数据比永久代中纯粹很多,就只是类的元数据,这些信息只对编译期或JVM的运行时有用
  3. 永久代有一个JVM本身设置固定大小上线,无法进行调整,而元空间使用的是直接内存,受本机可用内存的限制,并且永远不会OOM
  4. 符号引用没有存在元空间中,而是存在native heap中,这是两个方式和位置,不过都可以算作是本地内存,在虚拟机之外进行划分,没有设置限制参数时只受物理内存大小限制,即只有占满了操作系统可用内存后才OOM

堆区内存细分

对于大多数应用,Java 堆是 Java 虚拟机管理的内存中最大的一块,被所有线程共享。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数据都在这里分配内存。

为了进行高效的垃圾回收,虚拟机把堆内存逻辑上划分成三块区域(分代的唯一理由就是优化 GC 性能):

  1. 新生代:新对象和没达到一定年龄的对象都在新生代
  2. 老年代:被长时间使用的对象,老年代的内存空间应该要比年轻代更大
  3. 永久区

Java 虚拟机规范规定,Java 堆可以是处于物理上不连续的内存空间中,只要逻辑上是连续的即可,像磁盘空间一样。实现时,既可以是固定大小,也可以是可扩展的,主流虚拟机都是可扩展的(通过 -Xmx-Xms 控制),如果堆中没有完成实例分配,并且堆无法再扩展时,就会抛出 OutOfMemoryError 异常。

新生代 (Young Generation)

新生代是所有新对象创建的地方。当填充新生代时,执行垃圾收集。这种垃圾收集称为 Minor GC。新生代被分为三个部分——伊甸园(Eden Memory)和两个幸存区(Survivor Memory,被称为from/to或s0/s1),默认比例是8:1:1

  1. 大多数新创建的对象都位于 Eden 内存空间中
  2. 当 Eden 空间被对象填充时,执行Minor GC,并将所有幸存者对象移动到一个幸存区中
  3. Minor GC 检查幸存者对象,并将它们移动到另一个幸存区。所以每次,一个幸存区总是空的
  4. 经过多次 GC 循环后存活下来的对象被移动到老年代。通过设置新生代对象的年龄阈值来实现提升到老一代。
老年代(Old Generation)

老年代内存包含那些经过许多轮小型 GC 后仍然存活的对象。通常,垃圾收集是在老年代内存满时执行的。老年代垃圾收集称为 主GC(Major GC),通常需要更长的时间。

大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。这样做的目的是避免在 Eden 区和两个Survivor 区之间发生大量的内存拷贝。

JVM中对象在堆中的生命周期
  1. 在 JVM 内存模型的堆中,堆被划分为新生代和老年代
    • 新生代又被进一步划分为 Eden区Survivor区,Survivor 区由 From SurvivorTo Survivor 组成
  2. 当创建一个对象时,对象会被优先分配到新生代的 Eden 区
    • 此时 JVM 会给对象定义一个对象年轻计数器-XX:MaxTenuringThreshold
  3. 当 Eden 空间不足时,JVM 将执行新生代的垃圾回收(Minor GC)
    • JVM 会把存活的对象转移到 Survivor 中,并且对象年龄 +1
    • 对象在 Survivor 中同样也会经历 Minor GC,每经历一次 Minor GC,对象年龄都会+1
  4. 如果分配的对象超过了-XX:PetenureSizeThreshold(是大对象),会直接被分配到老年代
JVM中对象的分配过程

不仅需要考虑内存如何分配、在哪里分配等问题,并且由于内存分配算法和内存回收算法密切相关,所以还需要考虑 GC 执行完内存回收后是否会在内存空间中产生内存碎片。

  1. new 的对象先放在伊甸园区,此区有大小限制
  2. 当伊甸园的空间填满时,程序又需要创建对象,JVM 的垃圾回收器将对伊甸园区进行垃圾回收(Minor GC),将伊甸园区中的不再被其他对象所引用的对象进行销毁。再加载新的对象放到伊甸园区
  3. 然后将伊甸园中的剩余对象移动到幸存者 0 区
  4. 如果再次触发垃圾回收,此时上次幸存下来的放到幸存者 0 区,如果没有回收,就会放到幸存者 1 区
  5. 如果再次经历垃圾回收,此时会重新放回幸存者 0 区,接着再去幸存者 1 区
  6. 什么时候才会去养老区呢? 默认是 15 次回收标记
  7. 在养老区,相对悠闲。当养老区内存不足时,再次触发 Major GC,进行养老区的内存清理
  8. 若养老区执行了 Major GC 之后发现依然无法进行对象的保存,就会产生 OOM 异常
TLAB
  • 从内存模型而不是垃圾回收的角度,对 Eden 区域继续进行划分,JVM 为每个线程分配了一个私有缓存区域,它包含在 Eden 空间内
  • 多线程同时分配内存时,使用 TLAB 可以避免一系列的非线程安全问题,同时还能提升内存分配的吞吐量,因此我们可以将这种内存分配方式称为快速分配策略
  • OpenJDK 衍生出来的 JVM 大都提供了 TLAB 设计
为什么要有TLAB
  • 堆区是线程共享的,任何线程都可以访问到堆区中的共享数据
  • 由于对象实例的创建在 JVM 中非常频繁,因此在并发环境下从堆区中划分内存空间是线程不安全的
  • 为避免多个线程操作同一地址,需要使用加锁等机制,进而影响分配速度

尽管不是所有的对象实例都能够在 TLAB 中成功分配内存,但 JVM 确实是将 TLAB 作为内存分配的首选。

在程序中,可以通过 -XX:UseTLAB 设置是否开启 TLAB 空间。

默认情况下,TLAB 空间的内存非常小,仅占有整个 Eden 空间的 1%,我们可以通过 -XX:TLABWasteTargetPercent 设置 TLAB 空间所占用 Eden 空间的百分比大小。

一旦对象在 TLAB 空间分配内存失败时,JVM 就会尝试着通过使用加锁机制确保数据操作的原子性,从而直接在 Eden 空间中分配内存。

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

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

相关文章

21物联1班常用网络命令

常用网络命令 ipconfig(配置)ping(测试)命令1:ping 172.16.0.12:ping ip -t3:ping ip -l 3000(注意每个之间都存在空格)4:ping ip -n count netstat(网络)命令…

技术分享-上海泗博MPI转以太网模块MPI-131实现Node-RED直接访问西门子PLC数据

上海泗博自动化MPI-131是一款用于西门子S7系列PLC(包括S7-200、S7-300、S7-400)以及西门子数控机床(如840D、840DSL等)的以太网通讯模块,无需编程,即插即用,支持通过模块上下载PLC程序和数据监控…

【busybox记录】【shell指令】shuf

目录 内容来源: 【GUN】【shuf】指令介绍 【busybox】【shuf】指令介绍 【linux】【shuf】指令介绍 使用示例: 打乱内容 - 默认输出 打乱内容 - 最多输出n行 打乱内容 - 将输出写入文件 打乱内容 - 重复输出 打乱内容 - 打乱本条指令的参数 打…

Django调用MTP服务器给指定邮箱发送邮件

Django调用MTP服务器发送邮箱 邮箱的激活链接含有用户数据不能直接发送需要对其进行加密 发送邮箱是借助SMTP服务器进行中转 一. 配置SMTP服务中的邮箱信息以及激活链接 1. 配置邮箱权限 打开网易邮箱设置点击POP3 开启选项 注 : 在打开的过程中会弹出授权密码一点要保存 …

OpenAI泄密者加入马斯克xAI,技术版图扩张;OpenAI推出可识别DALL·E 3图像的AI检测工具

🦉 AI新闻 🚀 OpenAI泄密者加入马斯克xAI,技术版图扩张 摘要:最近,曾在OpenAI任职并被指控泄露机密的Pavel Izmailov迅速加入了马斯克旗下的xAI团队,成为研究员。在加入之前,Izmailov因涉嫌泄…

卸载系统自带APP

Firefly RK3588 android 12自动多个系统软件,无法从UI界面进行手动删除。因此,考虑使用shell指令进行处理。 系统自动APP大多都安装在system/app目录下,且该目录多为只读。因此采用如下步骤, //Shell su adb shell su //重新挂载…

Android 网络请求 实现

Android 网络请求 实现 一、背景 在Android开发中,网络请求是一个非常常见的需求。应用程序可能需要与远程服务器通信来获取数据、上传文件、验证用户身份等等。背景下,Android应用通常会面临以下几个主要情况和挑战: ①数据交互: 许多应用程序需要从服务器获取数据,例…

fabric搭建生产网络

fabric搭建生产网络 一、生成组织结构与身份证书 解包 hyperledger-fabric-linux-amd64-2.5.0.tar.gz 1.1、crypto-config.yaml配置文件 ./bin/cryptogen showtemplate > crypto-config.yaml 将crypto-config.yaml内容修改为: # -------------------------…

maven-test不通过导致无法打包

背景 别人写的一个test包,没有测试通过,导致最后没有打包成功 解决方案 package生命周中不要勾选test

2024-05-08 精神分析-对损失和挫败的强烈易感性-分析

摘要: 对损失的强烈的易感性,会在遭受损失或者挫败的时候,表现的极其敏感,这个过程主要是在创业的过程中更加强烈的表现并带来巨大的影响。必须要对其进行彻底的分析,并保持对此行为的长期的警惕。 所谓前事不忘后事之师&#x…

unity基础(二)

debug方法 Debug.Log(" 一般日志 ");Debug.LogWarning(" 警告日志 ");Debug.LogError(" 错误日志 ");// Player Informationstring strPlayerName "Peter";int iPlayerHpValue 32500;short shPlayerLevel 10;long lAdvantureExp 1…

Linux入门攻坚——22、通信安全基础知识及openssl、CA证书

Linux系统常用的加解密工具:OpenSSL,gpg(是pgp的实现) 加密算法和协议: 对称加密:加解密使用同一个秘钥; DES:Data Encryption Standard,数据加密标准&…

web 基础之 HTTP 请求

web 基础 网上冲浪 就是在互联网(internet)上获取各种信息,进行工作,或者娱乐,他的英文表示surfing the Internet,因 “surfing”d的意思是冲浪,即成为网上冲浪,这是一种形象说法, 也是一个非…

欧鹏RHCE 第四次作业

unit4.web服务的部署及高级优化方案 1. 搭建web服务器要求如下: 1.web服务器的主机ip:172.25.254.100 2.web服务器的默认访问目录为/var/www/html 默认发布内容为default‘s page 3.站点news.timinglee.org默认发布目录为/var/www/virtual/timinglee.org…

文件各种上传,离不开的表单 [html5]

作为程序员的我们,经常会要用到文件的上传和下载功能。到了需要用的时候,各种查资料。有木有..有木有...。为了方便下次使用,这里来做个总结和备忘。 利用表单实现文件上传 最原始、最简单、最粗暴的文件上传。 前端代码: //方…

如何更好地使用Kafka? - 故障时解决

要确保Kafka在使用过程中的稳定性,需要从kafka在业务中的使用周期进行依次保障。主要可以分为:事先预防(通过规范的使用、开发,预防问题产生)、运行时监控(保障集群稳定,出问题能及时发现&#…

Day 24 数据库管理及数据类型

数据库管理及数据类型 一:数据类型 1.数值类型 整数类型 ​ 整数类型:TINYINT SMALLINT MEDIUMINT INT BIGINT ​ 作用:用于存储用户的年龄、游戏的Level、经验值等 浮点数类型 ​ 浮点数类型:FLOAT DOUBLE ​ 作用&#xf…

Shell编程规范和变量

一.Shell脚本概述 Shell脚本的概念 将要执行的命令按顺序保存到一个文本文件给该文件可执行权限可结合各种Shell控制语句以完成更复杂的操作 Shell脚本应用场景 重复性操作交互性任务批量事务处理服务运行状态监控定时任务执行 Shell的作用 1)介于系统内核与用…

智能实训-wheeltec小车-抓取(源代码)

语言 :C 源代码&#xff1a; #include <ros/ros.h> #include <image_transport/image_transport.h> #include <cv_bridge/cv_bridge.h> #include <sensor_msgs/image_encodings.h> #include <sensor_msgs/JointState.h> #include <geometry…

SparkSQL优化

SparkSQL优化 优化说明 缓存数据到内存 Spark SQL可以通过调用spark.sqlContext.cacheTable("tableName") 或者dataFrame.cache()&#xff0c;将表用一种柱状格式&#xff08; an inmemory columnar format&#xff09;缓存至内存中。然后Spark SQL在执行查询任务…