JVM 内存和 GC 算法

文章目录

  • 内存布局
  • 直接内存
  • 执行引擎
    • 解释器
    • 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.rhkb.cn/news/182707.html

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

相关文章

Flink(一)【WordCount 快速入门】

前言 学完了 Hadoop、Spark,本想着先把 Kafka、Flume 这些工具先学完的,但想了想还是把核心的技术先学完最后再去把那些工具学学。 最近心有点累哈哈哈,偷偷立个 flag,反正也没人看,明年的今天来这里还愿哈&#xff0c…

深度学习之基于Yolov5人体姿态摔倒识别分析报警系统(GUI界面)

欢迎大家点赞、收藏、关注、评论啦 ,由于篇幅有限,只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 系统设计概述: 传感器采集:通过在场景中布置摄像头或红外传感器等设备,采集人体…

GZ035 5G组网与运维赛题第8套

2023年全国职业院校技能大赛 GZ035 5G组网与运维赛项(高职组) 赛题第8套 一、竞赛须知 1.竞赛内容分布 竞赛模块1--5G公共网络规划部署与开通(35分) 子任务1:5G公共网络部署与调试(15分) 子…

数学到底在哪里支撑着编程?

如果编程语言是血肉,那么数学的思想和知识就是灵魂。它可以帮助你选择合适的数据结构和算法,提升系统效率,并且赋予机器智慧。在大数据和智能化的时代更是如此。举个例子,我们在小学就学过的余数,其实在编程的世界里也…

python基础(Python高级特性(切片、列表生成式)、字符串的正则表达式、函数、模块、Python常用内置函数、错误处理)培训讲义

文章目录 1. Python高级特性(切片、列表生成式)a) 切片的概念、列表/元组/字符串的切片切片的概念列表切片基本索引简单切片超出有效索引范围缺省 扩展切片step为正数step为负数 b) 列表生成式以及使用列表生成式需要注意的地方概念举例说明1. 生成一个列…

Python详细教程,如何使用Python进行数据可视化?

文章目录 前言一、导入必要的库二、加载数据三、创建基本图表四、添加更多细节五、使用Seaborn库创建更复杂的图表关于Python技术储备一、Python所有方向的学习路线二、Python基础学习视频三、精品Python学习书籍四、Python工具包项目源码合集①Python工具包②Python实战案例③…

3D医学三维技术影像PACS系统源码

一、系统概述 3D医学影像PACS系统,它集影像存储服务器、影像诊断工作站及RIS报告系统于一身,主要有图像处理模块、影像数据管理模块、RIS报告模块、光盘存档模块、DICOM通讯模块、胶片打印输出等模块组成, 具有完善的影像数据库管理功能,强大…

人工智能AI 全栈体系(十二)

第二章 计算机是如何学会下棋的 下棋一直被认为是人类的高智商游戏,从人工智能诞生的那一天开始,研究者就开始研究计算机如何下棋。著名人工智能学者、图灵奖获得者约翰麦卡锡在 50 年代就开始从事计算机下棋方面的研究工作,并提出了著名的 …

北京陪诊小程序|陪诊系统开发|陪诊小程序未来发展不可小觑

近几年随着互联网快速发展,各行业领域都比较注重线上服务系统,通过陪诊小程序开发可以满足更多用户使用需求,同时还能提高用户使用体验。现在陪诊类的软件应用得到全面推广,在医疗行业当中陪诊小程序更贴近用户生活,可…

“七人拼团模式:创新玩法助力平台快速裂变引流“

七人拼团模式是一种结合了社交电商和拼购玩法的快速裂变引流模式。这种模式通过抽取平台营业所得作为奖励补贴用户,以更人性化的奖励机制吸引用户,服务用户,以此加快用户向粉丝的转变,为平台拉取有效流量。本文将介绍七人拼团模式…

什么是防火墙?详解三种常见的防火墙及各自的优缺点

目录 防火墙的定义 防火墙的功能 防火墙的特性 防火墙的必要性 防火墙的优点 防火墙的局限性 防火墙的分类 分组过滤防火墙 优点: 缺点: 应用代理防火墙 优点 缺点 状态检测防火墙 优点 缺点 防火墙的定义 防火墙的本义原是指古代人们…

DCU集群搭建虚拟环境方法简介

1.conda安装方法: wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh #下载miniconda安装包chmod 750 Miniconda3-latest-Linux-x86_64.sh #添加执行权限bash ./Miniconda3-latest-Linux-x86_64.sh #安装下载的minnconda32.集群安装…

VBA根据Excel内容快速创建PPT

示例需求:根据Excel中选中的单元格内容(3列)如下图所示,在已打卡的PowerPoint文件中创建页面。 新增PPT Slide页面使用第二个模板页面,其中包含两个文本占位符,和一个图片占位符。将Excel选中区域中前两列写…

c++实现观察者模式

前言 我觉得这是最有意思的模式&#xff0c;其中一个动&#xff0c;另外的自动跟着动。发布-订阅&#xff0c;我觉得很巧妙。 代码 头文件 #pragma once #include<vector> #include<string> #include<iostream>// 抽象观察者 class Aobserver { public:v…

xlua源码分析(二)lua Call C#的无wrap实现

xlua源码分析&#xff08;二&#xff09;lua Call C#的无wrap实现 上一节我们主要分析了xlua中C# Call lua的实现思路&#xff0c;本节我们将根据Examples 03_UIEvent&#xff0c;分析lua Call C#的底层实现。例子场景里有一个简单的UI面板&#xff0c;面板中包含一个input fie…

使用VSCODE链接Anaconda

打代码还是在VSCODE里得劲 所以得想个办法在VSCODE里运行py文件 一开始在插件商店寻找插件 但是没有发现什么有效果的 幸运的是VSCODE支持自己选择Python的编译器 打开VSCODE 按住CtrlShiftP 输入Select Interpreter 如果电脑已经安装上了Python的环境 VSCODE会默认选择普通…

yolov5--ptq--qat量化之敏感层分析

敏感层分析&#xff0c;应该是发生在ptq量化之前进行分析的操作&#xff0c;经过该操作&#xff0c;可得出哪些层不适合进行量化&#xff0c;则在接下来ptq时可以手动关闭这些层的量化。 进入敏感层分析函数sensitive_analysis中&#xff0c; 具体流程为&#xff1a; 首先验证…

安科瑞变电站综合自动化系统在青岛海洋科技园应用

安科瑞 耿敏花 摘 要&#xff1a;变电站综合自动化系统是将变电站内的二次设备经过功能的组合和优化设计&#xff0c;利用先进的计算机技术、通信技术、信号处理技术&#xff0c;实现对全变电站的主要设备和输、配电线路的自动监视、测量、控制、保护、并与上级调度通信的综合性…

UI设计感大型数据管理仪表盘后台模板源码

大型数据管理仪表盘后台模板是一款适合数据统计管理后台网站模板下载。提示&#xff1a;本模板调用到谷歌字体库&#xff0c;可能会出现页面打开比较缓慢。 演示下载 qnziyw点cn/wysc/qdmb/20838点html

软件测试/测试开发丨利用ChatGPT自动生成架构图

点此获取更多相关资料 简介 架构图通过图形化的表达方式&#xff0c;用于呈现系统、软件的结构、组件、关系和交互方式。一个明确的架构图可以更好地辅助业务分析、技术架构分析的工作。架构图的设计是一个有难度的任务&#xff0c;设计者必须要对业务、相关技术栈都非常清晰…