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 的开发者,在标记-清除算法的基础上改进,形成了标记-压缩算法。
标记-压缩算法也是分为两步:
- 第一阶段和标记-清除算法一样,标记所有被引用的对象。
- 将所有存活的对象压缩到内存的一端,按顺序排放,然后清理空间。
- 优点:解决了标记-清除算法的碎片化问题,消除了复制算法的内存减半的代价。
- 缺点:效率上来说还是低于复制算法,甚至低于标记-清除算法。对比标记-清除算法,标记-压缩算法有对象的移动,对应的引用地址就会涉及修改等。
标记-压缩算法的开销
- 标记(mark)阶段的开销与存活对象的数量成正比。
- 清除(sweep)阶段的开销与所管理的区域大小成正比。
- 压缩(Compact)阶段的开销与存活对象的数量成正比
分代垃圾回收
- 年轻代:区域相对老年代较小,对象生命周期短,存活率低,回收频繁。基于此特点,采用了复制算法进行垃圾回收,速度快,效率高,而且年轻代中两个幸存者区就是为了利用复制算法来划分的。
- 老年代:区域相对较大,对象存活率高,生命周期较长,回收不及年轻代频繁。基于此特点,采用的是标记-清除算法和标记-压缩算法混合使用的方式实现的垃圾回收。
增量收集算法
标记-清除、标记-压缩、复制算法,都或多或少的会有 stop the world 的出现,如果垃圾收集时间过长,应用程序会被挂起时间过长,影响用户体验。基于此情况,又诞生了增量收集算法。
如果一次性进行垃圾收集,将所有的垃圾进行处理,可能导致时间过长,所以增量收集算法就采用了垃圾收集线程和应用程序线程交替执行的思想,垃圾收集线程每次执行只收集一小片区域的内存空间,然后切换到应用程序线程执行,反复执行直到垃圾收集完成。(其本质上还是我们上面说到的垃圾收集算法,只是将垃圾收集和应用程序线程交替执行,以减少 stop the world 的时间)
- 优点:减少了 stop the world 的时间
- 缺点:交替执行线程,因为线程和线程上下文的切换的消耗,会使得垃圾回收的总成本上升,造成系统吞吐量下降。
分区算法
分区算法将堆空间划分为更小的区间,分代算法按照对象的生命周期长短划分成两个部分,分区算法将整个堆空间划分成连续的不同区域。每一个区域都独立使用,独立回收。这样一次回收多个小空间,降低了 stop the World 的时间。
相关文章:

JVM 内存和 GC 算法
文章目录 内存布局直接内存执行引擎解释器JIT 即时编译器JIT 分类AOT 静态提前编译器(Ahead Of Time Compiler) GC什么是垃圾为什么要GC垃圾回收行为Java GC 主要关注的区域对象的 finalization 机制GC 相关算法引用计数算法(Reference Count…...

memtest86 prosite v10.6
passmark官方的memtest86 v10开始支持颗粒级别的坏内存芯片定位了,对于特定的若干种CPU和芯片组的组合,支持这项功能。 当然支持颗粒定位的site版本售价4800美金,是比较贵的。所以网络上出现了破解版的,人才真是。但是鼓励大家支…...

Springboot JSP项目如何以war、jar方式运行
文章目录 一,序二,样例代码1,代码结构2,完整代码备份 三,准备工作1. pom.xml 引入组件2. application.yml 指定jsp配置 四,war方式运行1. 修改pom.xml文件2. mvn执行打包 五,jar方式运行1. 修改…...

系统架构设计师(第二版)学习笔记----层次式架构设计理论与实践
【原文链接】系统架构设计师(第二版)学习笔记----层次式架构设计理论与实践 文章目录 一、层次式体系结构概述1.1 软件体系结构的作用1.2 常用的层次式架构图1.3 层次式体系可能存在的问题点 二、表现层框架设计2.1 MVC模式2.1.1 MVC三层模式2.1.2 MVC设…...

Python之字符串详解
目录 一、字符串1、转义字符与原始字符串2、使用%运算符进行格式化 一、字符串 在Python中,字符串属于不可变、有序序列,使用单引号、双引号、三单引号或三双引号作为定界符,并且不同的定界符之间可以互相嵌套。 ‘abc’、‘123’、‘中国’…...

《视觉SLAM十四讲》-- 概述与预备知识
文章目录 01 概述与预备知识1.1 SLAM 是什么1.1.1 基本概念1.1.2 视觉 SLAM 框架1.1.3 SLAM 问题的数学表述 1.2 实践:编程基基础1.3 课后习题 01 概述与预备知识 1.1 SLAM 是什么 1.1.1 基本概念 (1)SLAM 是 Simultaneous Localization a…...

Java8 Stream API全面解析——高效流式编程的秘诀
文章目录 什么是 Stream Api?快速入门流的操作创建流中间操作filter 过滤map 数据转换flatMap 合并流distinct 去重sorted 排序limit 限流skip 跳过peek 操作 终结操作forEach 遍历forEachOrdered 有序遍历count 统计数量min 最小值max 最大值reduce 聚合collect 收集anyMatch…...

分享一下微信小程序里怎么开店
如何在微信小程序中成功开店:从选品到运营的全方位指南 一、引言 随着微信小程序的日益普及,越来越多的人开始尝试在微信小程序中开设自己的店铺。微信小程序具有便捷、易用、即用即走等特点,使得开店门槛大大降低。本文将详细介绍如何在微…...

uniapp小程序刮刮乐抽奖
使用canvas画布画出刮刮乐要被刮的图片,使用移动清除画布。 当前代码封装为刮刮乐的组件; vue代码: <template><view class"page" v-if"merchantInfo.cdn_static"><image class"bg" :src&q…...

Qt 窗口无法移出屏幕
1 使用场景 设计一个缩进/展开widget的效果,抽屉效果。 看到实现的方法有定时器里move窗口,或是使用QPropertyAnimation。 setWindowFlags(Qt::Dialog | Qt::FramelessWindowHint |Qt::X11BypassWindowManagerHint); 记得在移…...

java毕业设计基于springboot+vue线上教学辅助系统
项目介绍 本论文主要论述了如何使用JAVA语言开发一个线上教学辅助系统 ,本系统将严格按照软件开发流程进行各个阶段的工作,采用B/S架构,面向对象编程思想进行项目开发。在引言中,作者将论述线上教学辅助系统的当前背景以及系统开…...

开源 Wiki 软件 wiki.js
wiki.js简介 最强大、 可扩展的开源Wiki 软件。使用 Wiki.js 美观直观的界面让编写文档成为一种乐趣!根据 AGPL-v3 许可证发布。 官方网站:https://js.wiki/ 项目地址:https://github.com/requarks/wiki 主要特性: 随处安装&a…...

STM32基本定时器中断
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、STM32定时器的结构?1. 51定时器的结构1.1如何实现定时1s的功能? 2. stm32定时器的结构2.1 通用定时器 二、使用步骤1.开启时钟2.初始…...
学习历程_基础_精通部分_达到手搓的程度
1. 计算机网络(更新版) 1.1 计算机网络-43题 1.2 2. 操作系统(更新版) 3. ACM算法(更新版) 4. 数据库(更新版) 5. 业务开发算法(更新版) 6. 分布式类(更新版) 7. 设计模式(更新版ÿ…...

Redis中的List类型
目录 List类型的命令 lpush lpushx rpush lrange lpop rpop lindex linsert llen lrem ltrim lset 阻塞命令 阻塞命令的使用场景 1.针对一个非空的列表进行操作 2.针对一个空的列表进行操作 3.针对多个key进行操作. 内部编码 lisi类型的应用场景 存储(班级…...

3D模型格式转换工具HOOPS Exchange:如何将3D PDF转换为STEP格式?
3D CAD数据在制造、工程和设计等各个领域都扮演着重要的角色。为了促进不同软件应用程序之间的协作和互操作性,它通常以不同的格式进行交换。 HOOPS Exchange是一个强大的软件开发工具包,提供了处理和将3D CAD数据从一种格式转换为另一种格式的解决方案…...

DB-GPT介绍
DB-GPT介绍 引言DB-GPT项目简介DB-GPT架构关键特性私域问答&数据处理多数据源&可视化自动化微调Multi-Agents&Plugins多模型支持与管理隐私安全支持数据源 子模块DB-GPT-Hub微调参考文献 引言 随着数据量的不断增长和数据分析的需求日益增多,将自然语言…...
Java,面向对象,内部类
内部类的定义: 将一个类A定义在另一个类B里面,里面的那个类A就称为内部类(InnerClass),类B则称为外部类(OuterClass)。 内部类的使用场景: 类A只在类B中使用,便可以使用内部类的方法…...

唯一ID如何生成,介绍一下目前技术领域最常使用的几种方法
纵使十面大山,又如何,无妨… 概述 唯一ID(Unique Identifier)是在计算机科学和信息技术领域中用于标识某个实体或数据的唯一标识符。生成唯一ID的方法可以根据具体需求和应用场景的不同而有所不同。以下是一些目前技术领域中常用…...

【翻译】XL-Sum: Large-Scale Multilingual Abstractive Summarization for 44 Languages
摘要 当代的关于抽象文本摘要的研究主要集中在高资源语言,比如英语,这主要是因为低/中资源语言的数据集有限。在这项工作中,我们提出了XL-Sum,这是一个包含100万篇专业注释的文章摘要对的综合多样数据集,从BBC中提取&…...

VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...

.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
FFmpeg 低延迟同屏方案
引言 在实时互动需求激增的当下,无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作,还是游戏直播的画面实时传输,低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架,凭借其灵活的编解码、数据…...
前端倒计时误差!
提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...
多场景 OkHttpClient 管理器 - Android 网络通信解决方案
下面是一个完整的 Android 实现,展示如何创建和管理多个 OkHttpClient 实例,分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...
Linux简单的操作
ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...
django filter 统计数量 按属性去重
在Django中,如果你想要根据某个属性对查询集进行去重并统计数量,你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求: 方法1:使用annotate()和Count 假设你有一个模型Item,并且你想…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)
宇树机器人多姿态起立控制强化学习框架论文解析 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一) 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...

智能仓储的未来:自动化、AI与数据分析如何重塑物流中心
当仓库学会“思考”,物流的终极形态正在诞生 想象这样的场景: 凌晨3点,某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径;AI视觉系统在0.1秒内扫描包裹信息;数字孪生平台正模拟次日峰值流量压力…...

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中,新增了一个本地验证码接口 /code,使用函数式路由(RouterFunction)和 Hutool 的 Circle…...