JVM 内存和 GC 算法

news2024/11/29 4:36:21

文章目录

  • 内存布局
  • 直接内存
  • 执行引擎
    • 解释器
    • JIT 即时编译器
      • JIT 分类
      • AOT 静态提前编译器(Ahead Of Time Compiler)
  • GC
    • 什么是垃圾
    • 为什么要GC
    • 垃圾回收行为
    • Java GC 主要关注的区域
    • 对象的 finalization 机制
    • GC 相关算法
      • 引用计数算法(Reference Counting)
      • 可达性分析算法
          • GC Roots
      • Stop The World
      • 标记-清除算法(mark-sweep)
      • 复制算法(Copying)
      • 标记-压缩(标记-整理)算法
    • 分代垃圾回收
    • 增量收集算法
    • 分区算法

内存布局

  • 对象头(Header)
    • 运行时元数据(Mark word):哈希值、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳
    • 类型指针 :指向类元数据,确定对象所属类型。如果是数组还要记录数组的长度
  • 实例数据(Instance Data):类中的各类型变量以及父类的相关类型数据。
  • 对齐填充(Padding):非必须,也没有特殊含义,仅起到占位符的作用

直接内存

  • 直接内存不是JVM 运行时数据区的一部分,是 Java 堆外的,系统的内存区间。NIO 通过存在堆中的 DirectByteBuffer 操作 Native 内存。通常情况下,直接内存的运行效率优于 Java 堆,读写频繁的场合,如果NIO 库就允许 Java 程序使用直接内存。

直接内存在 Java 堆外,因此是不受 -Xmx 的限制的,但操作系统内存是有限的

  • -XX:MaxDirectMemorySize=1G,表示设置 NIO 可以操作的直接内存最大大小为 1G,默认为 0,表示 JVM 自动选择 NIO 可以操作的直接内存大小。

直接内存也会 OOM。

执行引擎

执行引擎(Execution Engine),将字节码指令解释/编译(注意与 Java 文件编译为 .class 文件的编译区分,有的地方称之为后端编译)为对应平台上的本地机器指令,实际就是将高级编程语言翻译为机器指令。

解释器

当 Java 虚拟机启动时会根据预定义的规范对字节码采用逐行解释的方式执行,将每条字节码文件内容“翻译”为对应平台的本地机器指令。此过程由解释器执行。

  • 字节码解释器:纯软件代码模拟字节码执行,效率低下。
  • 模板解释器:每一条字节码和一个模板函数相关联,模板函数直接产生这条字节码执行时的机器码,效率高。

基于解释器来执行,还是相对低效的,所以又有了 JIT 即时编译器。

JIT 即时编译器

JIT(Just In Time Compiler)即时编译器:就是 JVM 将源代码直接编译成和本地机器相关的机器语言。

  • 根据代码被调用的执行频率来确定是否需要启动 JIT 来进行编译,这些需要被 JIT 编译为本地指令的代码,称为“热点代码”,JIT 在运行时会针对这些热点代码做深度优化,以提高 Java 程序的性能。
  • 栈上替换:一个多次被调用的方法,或者一个方法体内部的循环次数较多的循环体,都可以称为“热点代码”,他们都可以通过 JIT 编译为本地机器指令。由于此过程发生在方法执行过程中,因此也称为栈上替换(OSR 编译)On Stack Replacement。
  • 热点探测功能:HotSpot 基于计数器的方式来进行热点探测热点代码被调用的次数。Client 模式 1500次,Server 模式 10000 次,才是热点代码,才切换为 JIT 编译。
  • 指令缓存:JIT 编译为本机指令代码后,会进行代码缓存,以提高性能。

此外我们还可以通过 java 命令设置,让程序使用纯解释器或纯 JIT 编译器执行,或者两者的混合执行的模式。

  • -Xint 纯解释器模式
  • -Xcomp 纯编译器模式
  • -Xmixed 混合模式(默认)

JIT 分类

JIT 分为如下两类编译器:

  • C1(Client Compiler)编译器:运行在 -client 模式下,C1 编译器会对字节码进行简单和可靠的优化,耗时短。
    • 方法内联
    • 去虚拟化
    • 冗余消除
  • C2(Server Compiler)编译器:运行在 -server 模式下,C2 进行耗时较长的优化,以及激进优化。但优化后的代码执行效率高。
    • 标量替换
    • 栈上分配
    • 同步消除
  • Graal 编译器:JDK 10+ 才加入的全新的即时编译器。

AOT 静态提前编译器(Ahead Of Time Compiler)

JIT 是程序运行中执行的,AOT 编译是在程序运行之前执行,将字节码转换为机器码的过程。好处:预编译.class 文件为 .os 文件

GC

什么是垃圾

垃圾是指在运行程序中没有任何指针指向的对象。如果不及时堆垃圾进行清理,这些垃圾会一直占用内存空间,直到程序运行结束,在这期间这些对象所占用的内存无法使用,造成极大的资源浪费。

为什么要GC

如果不 GC ,内存迟早会消耗完,导致新的对象无法分配内存,所以没有 GC 程序就可能无法正常执行。

垃圾回收行为

  • 手动GC:C / C++ 需要开发人员手动进行内存的申请和回收,这样做的好处是灵活,但坏处是操作太频繁,而且对开发人员的要求较高,相对增加了开发人员的负担。
  • 自动GC:Java 采用的是自动 GC 的机制。自动管理内存,无需开发人员手动分配和回收。坏处是弱化了开发人员对内存的管理,只能通过监控和调节相关参数来优化。

Java GC 主要关注的区域

  • 方法区(注:方法区对应永久代或元空间,很多 JVM 没有方法区的 GC)
  • 堆区

Java GC 的主要特点为:频繁收集 Young 区(年轻代),较少收集 Old 区(老年代),基本不收集 Perm 区(老年代、元空间)

对象的 finalization 机制

当垃圾回收器对垃圾对象进行垃圾回收之前,会先调用该对象的 finalize 方法,该方法在 Object 类中定义,可以被重写,主要用于在对象被回收时进行资源释放。我们通常在此方法中进行一些资源释放的操作和清理的操作,比如:File 、IO 操作的关闭、Socket 操作的关闭、数据库连接的关闭等等。

注:不要在程序中主动调用 finalize 方法,该方法仅提供给垃圾回收器调用

  • 在 finalize 时,可能导致对象复活
  • finalize 方法执行时间是没有保障的,它有 GC 线程决定,若不发生 GC,则不会执行。GC 时调用 finalize 方法是由单独的优先级较低一点的线程(Finalizer)来执行。执行前放在执行队列中(因为 GC 是有多个对象的 finalize 方法需要调用)
  • finalize 方法还可能导致 GC 失败,所以在重写该方法时要注意执行效率等。

由于 finalize 方法,对象可能会出现如下几个状态:

  • 可触及:该对象可达。(可达性算法分析)
  • 可复活:对象不可达,但对象可能调用 finalize 方法后复活。(在 finalize 方法中使当前对象跟引用链中任何一个对象建立联系,就会导致对象复活。但之后再次不可用,则不会调用 finalize 方法了。finalize 方法只调用一次)
  • 不可触及:finalize 方法被调用成功,且对象没有复活。只有此状态的对象才可不垃圾回收。

GC 相关算法

GC 分为两个阶段:标记阶段和清除阶段,每个阶段都有对应的算法,这些算法可以统称为垃圾回收算法。

  • 标记阶段:判断对象是否存活。其对应的算法有引用计数算法和可达性分析算法
  • 清除阶段:在判断对象释放存活之后,GC 接下来就会对死亡的对象进行垃圾回收,释放内存空间。目前常用的算法有,标记-清除算法(mark-sweep)、复制算法(Copying)、标记-压缩算法(mark-Compact)

引用计数算法(Reference Counting)

每个对象保存一个整型引用计数器,记录该对象被引用的情况。只要有任何一个地方引用了该对象,则该对象的计数器值 +1 ,如果不再引用了,则计数器值 -1,只要该对象的引用计数器的值为 0,则表示该对象死亡,可以被 GC 回收。

  • 优点:实现简单,垃圾对象判断简单,判断效率高,回收没有延迟性。
  • 缺点
    • 需要独立的字段存储计数器,增加内存开销
    • 任何引用的变动都需要更新计数器的值
    • 无法处理循环引用(这是个致命的问题,所以目前的 JVM 中都没有使用此算法了)
      在这里插入图片描述

可达性分析算法

可达性分析算法又叫根搜索算法或跟踪性垃圾收集,相对引用计数算法而言,可达性分析算法不仅同样具备实现简单和执行高效等特点而且可以有效的解决循环引用问题,防止内存泄漏的问题发生。Java 就是选择的可达性分析算法。

  • 可达性分析算法是以根对象集合(GC Roots)为起始点,按照从上到下的方式搜索被根对象集合所连接的目标对象是否可达。
  • 使用可达性分析算法后,内存中的存活对象都会被根对象集合直接或间接连接着,搜索走过的路径称为引用链(Reference Chain)
  • 如果目标对象没有任何引用链相连,则对象释不可达的,可以标记为垃圾对象。只有直接或间接被根对象连接的对象才是存活对象。
GC Roots

GC Roots 包含如下几类元素:

  • JVM 中引用的对象
    • 如:各个线程被调用的方法中使用的参数、局部变量等。
  • 本地方法栈内 JNI (本地方法)引用的对象
  • 方法区中静态属性引用的对象
    • 如:对象类型的静态变量
  • 方法区中常量引用的对象
    • 如:字符串常量池中的引用对象
  • 被同步锁 synchronized 持有的对象
  • JVM 内部的引用
    • 如:系统类加载器、Class 对象等
  • 反应 JVM 内部情况的 JMXBean 、JVMTI 中的注册回调、本地代码缓存等。

Stop The World

如果要使用可达性分析算法来判断内存是否可以回收,那么分析工作必须在一个能保障一致性的快照中进行,否则分析结果无法保证完全正确。所以在 JVM 在进行 GC 时,必须 “Stop The World” 用户线程出现停顿。

标记-清除算法(mark-sweep)

标记-清除算法就分为标记和清除两个阶段:

  • 标记:收集器从根节点开始遍历,标记所有被引用的对象(注意:这里是标记的可用对象,标记的内容标记在对象头 Header 中)
  • 清除:收集器从堆内存从头到尾的线性遍历,如果发现某个对象没有标记为可用对象,则将其回收。

标记-清除算法简单明了,但在进行 GC 的时候需要停止整个应用程序,而且清理出来的内存空间是不连续的,容易产生内存碎。可能导致可用空间碎片化,不能整体存放大对象。而且该算法需要经历标记-清除两步,意味着需要两次遍历对象。

在这里插入图片描述

标记-清除算法的清除并不是真的清除,只是将可回收的对象的地址进行记录(放在空闲列表),当有新对象来的时候,进行覆盖。

复制算法(Copying)

将内存空间分为两块,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后清除正在使用的内存块中的所有对象,然后交换两个内存块的角色,最后完成垃圾回收。(这里是不是想起了什么?我们的两个幸存者区就是使用的该方式)

在这里插入图片描述
优点:一次遍历,更高效,复制后内存连续,没有碎片化问题。
缺点:需要两份内存空间,且任何时刻都有一个空间不使用,而且对象需要复制,当存活对象很多的情况下,复制所占用的系统资源也不少。对于存活对象不多的情况比较适用。(想想为什么在年轻代中的幸存者区使用该算法)

标记-压缩(标记-整理)算法

在年轻代中,一般只有少部分对象存活,使用复制算法,可以有效利用复制算法的优点,但在老年代中,存活对象更多,采用复制算法就会放大其缺点,所以 JVM 的开发者,在标记-清除算法的基础上改进,形成了标记-压缩算法。

标记-压缩算法也是分为两步:

  1. 第一阶段和标记-清除算法一样,标记所有被引用的对象。
  2. 将所有存活的对象压缩到内存的一端,按顺序排放,然后清理空间。
    在这里插入图片描述
  • 优点:解决了标记-清除算法的碎片化问题,消除了复制算法的内存减半的代价。
  • 缺点:效率上来说还是低于复制算法,甚至低于标记-清除算法。对比标记-清除算法,标记-压缩算法有对象的移动,对应的引用地址就会涉及修改等。

标记-压缩算法的开销

  • 标记(mark)阶段的开销与存活对象的数量成正比。
  • 清除(sweep)阶段的开销与所管理的区域大小成正比。
  • 压缩(Compact)阶段的开销与存活对象的数量成正比

分代垃圾回收

  • 年轻代:区域相对老年代较小,对象生命周期短,存活率低,回收频繁。基于此特点,采用了复制算法进行垃圾回收,速度快,效率高,而且年轻代中两个幸存者区就是为了利用复制算法来划分的。
  • 老年代:区域相对较大,对象存活率高,生命周期较长,回收不及年轻代频繁。基于此特点,采用的是标记-清除算法和标记-压缩算法混合使用的方式实现的垃圾回收。

增量收集算法

标记-清除、标记-压缩、复制算法,都或多或少的会有 stop the world 的出现,如果垃圾收集时间过长,应用程序会被挂起时间过长,影响用户体验。基于此情况,又诞生了增量收集算法。

如果一次性进行垃圾收集,将所有的垃圾进行处理,可能导致时间过长,所以增量收集算法就采用了垃圾收集线程和应用程序线程交替执行的思想,垃圾收集线程每次执行只收集一小片区域的内存空间,然后切换到应用程序线程执行,反复执行直到垃圾收集完成。(其本质上还是我们上面说到的垃圾收集算法,只是将垃圾收集和应用程序线程交替执行,以减少 stop the world 的时间)

  • 优点:减少了 stop the world 的时间
  • 缺点:交替执行线程,因为线程和线程上下文的切换的消耗,会使得垃圾回收的总成本上升,造成系统吞吐量下降。

分区算法

分区算法将堆空间划分为更小的区间,分代算法按照对象的生命周期长短划分成两个部分,分区算法将整个堆空间划分成连续的不同区域。每一个区域都独立使用,独立回收。这样一次回收多个小空间,降低了 stop the World 的时间。

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

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

相关文章

【嵌入式开发学习】__软件工程师的关键原则-18个系统设计概念

目录 前言 01. 域名系统 (DNS) 02. 负载均衡器 03. API 网关 04. 内容交付网络 (CDN) 05. 正向代理与反向代理 06. 缓存 07. 数据分区 08. 数据库复制 09. 分布式消息系统 10. 微服务 11. 数据库 12. 前端缓存 13. 后端缓存 14. 安全性 15. 高可用性与容错性 …

阿里云双11优惠活动:新老用户99元服务器和代金券领取攻略

2023阿里云双11优惠活动开启了,轻量2核2G3M带宽服务器87元一年、2核4G4M带宽165元一年,云服务器ECS经济型e实例2核2G3M固定带宽优惠价格99元一年,新老用户同享,并且续费不涨价,第二年99元续费。阿里云个人和企业用户还…

JAVA技术栈的有福啦!这款IDEA插件,写完代码即可调试

国产API调试工具 Apipost 推出IDEA插件,写完代码就可以调试接口并一键生成接口文档!而且还可以根据已有的方法帮助您快速生成 url 和 params。Apipost Helper API 调试工具 API 管理工具 API 搜索工具。 在商店中搜索或直接点击下方链接即可下载&…

天猫商品评论API接口(评论内容|日期|买家昵称|追评内容|评论图片|评论视频..)

要获取天猫商品评论接口,您需要使用天猫开放平台提供的API接口。以下是一些可能有用的步骤: 注册并登录天猫开放平台,获取开发者账号。在开发者中心创建一个应用,获取应用的App Key和App Secret。使用天猫开放平台的API接口&…

Yolov8目标识别与实例分割——算法原理详细解析

前言 YOLO是一种基于图像全局信息进行预测并且它是一种端到端的目标检测系统,最初的YOLO模型由Joseph Redmon和Ali Farhadi于2015年提出,并随后进行了多次改进和迭代,产生了一系列不同版本的YOLO模型,如YOLOv2、YOLOv3、YOLOv4&a…

【进程控制⑦】:制作简易shell理解shell运行原理

【进程控制⑦】:制作简易shell&&理解shell运行原理 一.交互问题,获取命令行二.字串分割问题,解析命令行三.指令的判断四.普通命令的执行五.shell原理本质 一.交互问题,获取命令行 shell刚启动时就会出现一行命令行&#x…

如何建立个人百科,事半功倍

在现代社会,人们越来越重视个人品牌建设,而打造个人百科是其中重要的一环。那么,什么是个人百科呢?简单来说,它是一本介绍个人生平、成就和贡献的百科全书,其好处不言而喻。 首先,它可以提高个人…

vm虚拟机逆向---[GWCTF 2019]babyvm 复现【详解】

文章目录 前言题目分析CD1mov指令xor指令and其他指令 E0BF83 汇编提取编写exp 前言 好难。大家都觉得简单,就我觉得难是吧,,,,, 题目分析 、 代码看着挺少,三个主要函数,一开始好像…

足足68个!Python函数合集请收好!

内置函数就是python给你提供的, 拿来直接用的函数,比如print.,input等。 #68个内置函数 # abs()   dict()   help()   min()   setattr() # all()   dir()   hex()   next()   sli…

git clone 报错:fatal: unable to access ‘https://github.com/XXXXXXXXX‘

国内使用GIT工具,拉取github代码,会因为网络原因无法成功拉取。出现如下类似情形: 此时更改 web URL即可,改用镜像的github网站替换https://github.com/。即URL里的https://github.com/换成https://hub.nuaa.cf/,即可…

鸿蒙应用开发之后台代理提醒

一、简介 随着生活节奏的加快,我们有时会忘记一些重要的事情或日子,所以提醒功能必不可少。应用可能需要在指定的时刻,向用户发送一些业务提醒通知。例如购物类应用,希望在指定时间点提醒用户有优惠活动。为满足此类业务诉求&…

取消elementUI中table的选中状态

1、表格上绑定ref 2、清空用户选中数据 this.$refs.loopRef.clearSelection()

ATE新能源汽车充电桩自动负载测试系统

随着新能源汽车的普及,充电桩的需求也在不断增加,为了确保充电桩的性能和安全性,对其进行负载测试是非常重要的。ATE新能源汽车充电桩自动负载测试系统是一种专门用于检测充电桩性能的设备,它可以模拟各种实际使用场景&#xff0c…

swift语言下SurfGen库做的爬虫是什么样的 ?

Swift语言并没有内置的爬虫库,但是你可以使用第三方库来实现爬虫功能。其中比较常用的是Alamofire和SwiftyJSON。Alamofire是一个基于Swift语言的HTTP网络库,可以用来发送HTTP请求和接收HTTP响应。而SwiftyJSON则是一个用于处理JSON数据的Swift库&#x…

22款奔驰GLS450升级迈巴赫踏板 上下车更加方便

原车升级前今天来一台奔驰GLS升级电动踏板,这台车原车是带固定踏板的,但是车主觉得不如电踏好,就来升级一套迈巴赫1:1的电动踏板。电动踏板我们也是经常改的项目,因为大型车没有个踏板确实不太方便。

数智赋能!麒麟信安参展全球智慧城市大会

10月31日至11月2日,为期三天的2023全球智慧城市大会长沙在湖南国际会展中心举办,大会已连续举办12届,是目前全球规模最大、专注于城市和社会智慧化发展及转型的主题展会。长沙市委常委、常务副市长彭华松宣布开幕,全球智慧城市大会…

2023最新版本 FreeRTOS教程 -3-消息队列-验证(动态创建)

队列概述 一块可读写的特殊缓冲区,读取空会导致任务挂起,以此来优化MCU的使用率 API函数 创建 g_xQueuePlatform xQueueCreate(10, //队列长度sizeof(struct input_data)//队列中每一个块的大小); 写入 xQueueSend(g_xQueuePlatform, &idata, 0);读取 xQueueReceive(g…

如何让企业配件管理高效又智能!仓库配件出入库管理系统哪家的好用?

在当今快速发展的商业环境中,企业运营的效率和管理的重要性日益凸显。对于许多企业来说,仓库配件管理是一个关键的环节,它不仅涉及到物品的存储和分发,还与企业的成本控制和运营流程紧密相关。然而,管理仓库配件是一项…

Oracle-执行计划生成及查看的几种方法

1. EXPLAIN FOR 语法: EXPLAIN PLAN FOR SQL语句SELECT * FROM TABLE(dbms_xplan.display());优点: 无需真正执行SQL 缺点: 没有输出相关的统计信息,例如产生了多少逻辑读、物理读、递归调用等情况无法判断处理了多少行无法判断…

跨境商城开发指南:10个必备步骤助您实现国际化零售业务飞跃

欢迎阅读本篇文章,作为跨境商城开发领域的专家,我将为您提供一份全面的指南,帮助您实现国际化零售业务的飞跃。在如今全球化的商业环境下,跨境电商已成为许多企业拓展市场的重要途径。通过合理规划和正确执行,您可以在…