JVM系列 | 垃圾收集算法
JVM系列 | 垃圾收集算法
文章目录
- 前言
- 如何判断对象已"死"?
- 引用计数法
- 可达性分析算法
- 可达性分析2.0版 | 引用的增强
- 对象的消亡过程
- 回收方法区
- 主要回收目标:
- 回收操作
- 垃圾收集算法
- 分代收集理论 与 跨代引用假说
- 分代收集理论
- 跨带引用假说
- 垃圾收集算法 | 标记清除算法
- 垃圾收集算法 | 标记复制算法
- 传统标记复制算法
- 优化标记复制算法
- 逃生门 | 提前养老
- 垃圾收集算法 | 标记整理算法
前言
在上一篇文章《【JVM】对象的生命周期一 | 对象的创建与存储》中,我们已经介绍了对象的完整创建过程,既然有出生,那么必然有死亡。本文将会详细介绍对象的消亡-JVM的垃圾回收机制。
这两篇文章详细介绍了对象的生命周期,推荐联合观看。
如何判断对象已"死"?
引用计数法
JVM 并不是使用引用计数器来判断对象是否存活的,这是由于引用计数器有着非常大的缺点。
引用计数法非常的简单:在对象中添加一个引用计数器,每当有一个地方引用它,计数器的值就+1,当引用失效时,计数器-1。当计数器为0时,就代表没有地方引用它,它就可以被垃圾回收器清除掉。
想法很完美,但是,如果两个对象互相引用,那么即使这两个对象已经不存在其它的调用关系,也不会被垃圾收集算法清理掉,请看以下代码:
class Node {Node next;
}public class ReferenceCountingExample {public static void main(String[] args) {Node node1 = new Node();Node node2 = new Node();// 互相引用node1.next = node2;node2.next = node1;// 取消外部引用node1 = null;node2 = null;// 在引用计数垃圾回收器中,node1 和 node2 由于互相引用,// 引用计数永远不会变为0,它们不会被回收。}
}
以上代码中:
- 创建两个node对象,每个node对象的引用计数器为1
- 让他们的nextNode指向对方,现在互相引用,每个node的引用计数器是2
- 清除外部引用,也就是让node1/node2变为null,此时每个node的引用计数器是1
- 没有地方再引用node1/2了,但是node1/2还是无法正常退出
可达性分析算法
可达性分析算法从根对象(GC Roots)出发(注意根对象可以不止有一个,下面会有介绍),一级一级向下扫描,能被根对象直接或间接引用的对象就是存活对象(从根对象可达),与根对象没有任何关系的对象则为消亡的对象(根对象不可达)。

上图中:
- O2/O3/O4与根对象O1(间接)可达,那么在本次扫描中,该三个对象全部为存活
- O6与O7对象虽然与O5对象关联,但是O5对象并没有与根节点关联,因此O5/O6/O7对象全部消亡
- O8/O9对象虽然互相引用,但是也不例外,消亡
可达性分析2.0版 | 引用的增强
在Java 1.2之前,引用只有传统的实现方式:可达即存活、不可达即消亡。
但是在一些场景下,有一些对象存在能创造一定的价值,但是消亡了意义也不大,典型的例子就是缓存。缓存中存在的内容可能是一些大的对象,我们通过缓存可以加快程序的运行速度。但是如果缓存的内容太多,那么会严重影响JVM的运行速度,此时Java都跑不动了,还关心数据库读取什么的嘛?这个时候就可以释放掉这些引用内容。
为了解决这一问题,JDK引入了另外三种引用方式,分别如下:
- 强引用(经典引用 Strongly Reference)
- 软引用(Soft Reference)
- 弱引用(Weak Reference)
- 虚引用(Phantom Reference)
强引用是最传统的“引用”的定义,是指在程序代码之中普遍存在的引用赋值,即类似“Object obj=new Object()”这种引用关系。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。
软引用是用来描述一些还有用,但非必须的对象。只被软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常。在JDK 1.2版之后提供了SoftReference类来实现软引用。
弱引用也是用来描述那些非必须对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK 1.2版之后提供了WeakReference类来实现弱引用。
虚引用也称为“幽灵引用”或者“幻影引用”,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知。在JDK 1.2版之后提供了PhantomReference类来实现虚引用。
对象的消亡过程
上文中我们已经确定了判断对象的消亡的方法,但是并不是一旦发现消亡对象之后就立刻进行清除,要清除一个对象至少要经过两次标记阶段。
阶段一:判定对象为不可达
阶段二:判断对象是否重写了finalize()方法(终结器方法),如有没有重写该方法,则直接进行垃圾回收/如果重写了该方法那么将会把该对象放在名为F-Queue队列中,并在稍后由虚拟机自动建立的、低调度优先级的Finalizer线程去执行它们的finalize()方法。这里所说的“执行”是指虚拟机会触发这个方法开始运行,但并不承诺一定会等待它运行结束(以防止执行缓慢或死循环等)。
阶段三:对F-Queue队列中的对象进行二次标记。此时会查看这些对象是否仍然不可达,如果不可达那么这些对象将会被垃圾回收器进行回收。
如过finalize方法中,有代码把该对象的引用重新赋值给某个静态变量或其他存活的对象(该对象又可达了),那么该对象将不会被垃圾回收掉。
可见,对象的消亡像是一个判刑的过程,如果对象一开始犯了错(不可达),那么就先要判断该对象有没有必要缓刑(重写finalize方法),然后JVM将不缓刑的对象直接死刑立即执行,将需要缓刑的对象关到一个单独的监牢里面,随后给缓刑的对象们一个"托关系"的机会,找到机会了就能活,没有机会就得消亡。
回收方法区
简单复习:方法区是用于存储已被虚拟机加载的类信息(类名、访问修饰符、父类、接口、字段、方法等)、常量、静态变量、即时编译器编译后的代码(字段的名称与描述符、方法的字节码、访问修饰符)等数据。方法区在JVM规范中是堆的一部分,但在实现上可以有不同的划分和管理方式。
对方法区的回收性价比很低,在Java堆中,对常规应用进行一次垃圾收集通常可以回收70%至99%的内存空间,方法区则远低于此。因此也有一些垃圾收集器没有实现对方法区的回收。
主要回收目标:
- 废弃的常量
- 不再使用的类型
回收操作
回收常量:比较简单,如果一个字符串"Jim.kk"进入到常量池但是又没有任何一个字符串对象值是它,那么他就可以被清理出去。
回收不再使用的类型:回收不再使用的类型比较麻烦,它要同时满足以下条件才能够允许被回收。而且并不一定会被回收,需要程序员使用参数控制。
- 该类所有的实例都已经被回收,也就是Java堆中不存在该类及其任何派生子类的实例。
- 加载该类的类加载器已经被回收,这个条件除非是经过精心设计的可替换类加载器的场景,如OSGi、JSP的重加载等,否则通常是很难达成的。
- 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
启用条件:关于是否要对类型进行回收,HotSpot虚拟机提供了-Xnoclassgc参数进行控制,还可以使用-verbose:class以及-XX:+TraceClass-Loading、-XX:+TraceClassUnLoading查看类加载和卸载信息,其中-verbose:class和-XX:+TraceClassLoading可以在 Product版的虚拟机中使用,-XX:+TraceClassUnLoading参数需要FastDebug版[1]的虚拟机支持。
在大量使用反射、动态代理、CGLib等字节码框架,动态生成JSP以及OSGi这类频繁自定义类加载器的场景中,通常都需要Java虚拟机具备类型卸载的能力,以保证不会对方法区造成过大的内存压力。
垃圾收集算法
分代收集理论 与 跨代引用假说
分代收集理论
当我们使用引用可达算法扫描堆内存的时候,会发现大部分的对象都是朝生夕死的,存活时间可能根本不会超过一次垃圾收集。还有一些对象是长命百岁的,能一直活,很能活。
对于这种情况,垃圾收集器提出了新生代与老年代的概念:所有新创建的对象放在新生代中并定时进行扫描与垃圾回收,能挺过多次垃圾回收的对象将被放入老年代中,并在老年代中以更低的频率来回收该区域。这就同事兼顾了垃圾收集的时间开销和内存的空间利用。
跨带引用假说
对象与对象之间可能存在跨代引用,比如老年代引用新生代的对象,但是新生代的大部分都是朝生夕死的,所以跨代引用必定是小数(如果广泛存在的话则大部分新生代对象肯定都能存活很久),没有必要为了这一小部分跨带引用去扫描整个老年代,因此JVM建立了一个称为"记忆集"的数据结构(存储在新生代中),用来记录老年代的哪一块老年代的内存存在跨带引用,这样的话在扫描新生代时,只需要扫描一下这些被记录的老年代即可。
上图中,在第二个与第五个老年代的分片上存在跨代引用(分别是o7引用Y11/o20引用Y25),将这两个区域记录在记忆集中,随后在对新生代进行垃圾收集的时候,从记忆集中拿到两个老年代的内存区域并进行扫描,所以最终扫描对象除了所有新生代的对象以外,还包含(o5、o6、o7、o8、o17、o18、o19、o20)。
事实上并不只是跨老年代与新生代之间才存在跨代引用与记忆集,许多的分代或者分区的垃圾收集器中都存在跨代引用,比如近些年很火的G1收集器,没有明确的新生代与老年代,整个堆内存就是无数的小分区。
垃圾收集算法 | 标记清除算法
标记清除算法是最早出现的算法,在1960年有Lisp之父John McCarthy提出(当时还没有Java语言,不止是只有Java才有虚拟机与垃圾回收)。
标记清除算法分为两个步骤:1. 标记 2. 清除。可以对所有需要回收的对象做标记,随后统一清除掉;也可以对不需要回收的对象做标记,随后统一清除掉没有标记的对象。
标记回收算法有两个缺点:
- 是执行效率不稳定,如果Java堆中包含大量对象,而且其中大部分是需要被回收的,这时必须进行大量标记和清除的动作,导致标记和清除两个过程的执行效率都随对象数量增长而降低;
- 内存空间的碎片化问题,标记、清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致当以后在程序运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
在之前一篇文章《【JVM】对象的生命周期一 | 对象的创建与存储》中提到过使用空闲列表来记录堆内存中的空闲内存,随后在空闲内存中插入新对象的方式。这种方式就适用于标记清除算法,在一段时间使用后堆内存中会存在大量的空隙,造成很严重的内存浪费。
垃圾收集算法 | 标记复制算法
传统标记复制算法
标记复制算法又称为标记移动算法,它解决了标志清除算法中大量内存空间碎片的问题。
简单来说,标记复制算法就是将需要进行垃圾回收的区域分为两个部分(可以是新生代也可以是老年代),在创建新的对象的时候只使用其中的一半,在需要进行垃圾回收的时候,先对对象进行标记,然后将能存活的对象复制到另一半,当前区域的对象全部消亡(注意当前区域与目标区域不是新生代与老年代的区别)。
标记复制算法也有一个非常大的缺点:内存空间浪费,标记复制算法总有一半的内存空间是未被使用的。
优化标记复制算法
为了解决标记复制算法带来的巨大空间浪费问题,Andrew Appel针对具备“朝生夕灭”特点的对象,提出了一种更优化的半区复制分代策略,现在称为“Appel式回收”。
Appel(不是Apple哦)式回收采用一个大的Eden空间两个小的Survivor空间(Eden:Survivor通常为8:1),新建对象时将对象存放至Eden空间,进行垃圾回收时,扫描Eden空间与其中一块Survivor空间,并将存活的对象全部移动至另一块Survivor空间。
上一次垃圾回收存活的对象放在Survivor1中,本次垃圾回收扫描Eden空间与Survivor1空间,并将所有存活对象放入另一个Survivor2空间中,以此循环。
逃生门 | 提前养老
虽然朝生夕死大部分情况下可以消灭98%的对象,但是毕竟也会有特殊情况,万一那10%的Survivor无法存储本次GC可以存活下来的对象怎么办呢?Appel式垃圾回收提出了逃生门机制:
在通常情况下,对象进入老年代存在一个阈值,比如一个对象连续存活超过20次可以进入老年代。但是当触发逃生门机制的时候(Survivor无法存放全部存活对象),就会让一部分存活了一段时间但是还未达到阈值的对象提前进入老年代,这样可以保证Survivor空间的正常。
垃圾收集算法 | 标记整理算法
标记整理算法在需要GC时,会先标记所有对象,然后将不需要存活的对象从内容空间中剔除,给存活的对象整齐的复制到内存的开端。
标记整理算法相比于标记移动算法,节省了内存空间,但是移动所有的存活对象并更新所有引用是一件及其负重的操作,而且在这一阶段之内用户线程无法继续执行(否则可能会造成空引用等问题),因此这样的停顿被最初的虚拟机设计者描述为“Stop The World”(有点类似于时停的意思)。
相关文章:
JVM系列 | 垃圾收集算法
JVM系列 | 垃圾收集算法 文章目录 前言如何判断对象已"死"?引用计数法可达性分析算法可达性分析2.0版 | 引用的增强对象的消亡过程回收方法区主要回收目标:回收操作 垃圾收集算法分代收集理论 与 跨代引用假说分代收集理论跨带引用假说 垃圾收…...
深入理解Spring Boot中的事件驱动架构
深入理解Spring Boot中的事件驱动架构 大家好,我是微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿! 1. 引言 事件驱动架构在现代软件开发中越来越受欢迎,它能够提高系统的松耦合性和可扩展性。Sprin…...
Moldflow安装包下载:附网盘地址+详细教程步骤
如大家所了解的,Autodesk Moldflow仿真软件具有注塑成型仿真工具,能够帮助您验证和优化塑料零件、注塑模具和注塑成型流程。目前常用的版本有Moldflow 2019和Moldflow2023。 还没有获取Moldflow软件安装包资源的小伙伴,可以用百度云盘保存或下…...
2024辽宁省数学建模B题【钢铁产品质量优化】思路详解
2024 辽宁省大学数学建模竞赛试题 B 题 钢铁产品质量优化 由于连续退火工序中各阶段的工艺参数之间存在耦合性(加热炉的温度设定会影响后续均热与冷却温度的设定,以及带钢穿行速度),导致难以建立该工序的机理模型,从而…...
C++基础入门(上)
个人主页:C忠实粉丝 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C忠实粉丝 原创 C基础入门(上) 收录于专栏【C语法基础】 本专栏旨在分享学习C的一点学习笔记,欢迎大家在评论区交流讨论💌 目录 1. C发展历史 2. C版本…...
基于深度学习的情感分析
基于深度学习的情感分析是一种利用深度学习技术从文本数据中提取情感信息,判断文本的情感倾向(如正面、负面或中性)的方法。这项技术在市场营销、客户服务、社交媒体分析、产品评价和政治分析等领域有广泛应用。以下是对这一领域的系统介绍&a…...
mybatis 延迟加载
MyBatis的延迟加载(Lazy Loading)是一种优化技术,用于在需要时才加载关联对象或集合,从而提高性能和效率。以下是对MyBatis延迟加载的详细介绍: 延迟加载的基本概念 延迟加载是指在第一次访问对象的属性时才加载该对象…...
使用QT5.14.2开发族谱管理软件过程记录
目标缘由:出生在农村、学习了电脑技术,总有一个想法就是将老家传承下来的族谱录入电脑中,方便快速查询和长期保存。开始入手时候发现还挺有难度。 难点如下: 过去族谱纸质版书籍是民国时候印刷的、很多字都是繁体字、还有好些字…...
【QT】布局管理器
布局管理器 布局管理器1. 垂直布局2. 水平布局3. 网格布局4. 表单布局5. Spacer 布局管理器 之前使⽤ Qt 在界⾯上创建的控件, 都是通过 “绝对定位” 的⽅式来设定的;也就是每个控件所在的位置, 都需要计算坐标, 最终通过 setGeometry 或者 move ⽅式摆放过去。 …...
兼容问题---ios底部的安全距离css设置
在H5上适配安全区域:采用viewportenvconstant方案。 具体操作如下: 1. 需要将viewport设置为cover,env和constant才能生效。设置代码如下: <meta name"viewport" content"widthdevice-width,initial-scale1.…...
python JSON Lines (JSONL)的保存和读取;jsonl的数据保存和读取,大模型prompt文件保存常用格式
1. JSON Lines (JSONL)文件保存 将一个包含多个字典的列表保存为 JSON Lines (JSONL) 格式的文件,每个字典对应一个 JSONL 文件中的一行。以下是如何实现这一操作的 Python 代码 import json# 定义包含字典的列表 data [{"id": 1, "name": &qu…...
Spring Boot中@Async注解的使用及原理 + 常见问题及解决方案
😄 19年之后由于某些原因断更了三年,23年重新扬帆起航,推出更多优质博文,希望大家多多支持~ 🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志 🎐 个人CSND主页——Mi…...
ubuntu基于cmakelist的Qt工程,如何将图片打包进二进制程序
qt界面使用的图片打包进入二进制可执行程序,可以避免发布的软件,因为路径问题无法加载图片的问题。 以下步骤参考自百度AI. 步骤如下: 1.创建一个新的Qt资源文件(.qrc文件) 2.在*.qrc文件中添加图片路径 qrc文件使用…...
Spring的启动流程refresh方法、配置类解析流程@Component、@Configuration、@Import、@Bean
Spring的启动流程概述: 核心方法: refresh方法,作用就是实例化spring容器中的所有单例。 3步: 生成BeanFactory容器(有beanDefinition类信息和bean对象实例)生成BeanDefinition类信息生成bean对象实例 需…...
运算放大器(2)
(1)反向放大器 Vout(-R2/R1)*Vi 图一运放的同向端接地0V,反向端和同向端虚短,所以也是0V 反向输入端输入电阻很高,虚断,几乎没有电流注入和流出,那么R1和R2相当于是串联的,流过一个…...
智能优化算法之模拟退火算法SA
发展历史和算法思想 模拟退火算法(Simulated Annealing, SA)是一种基于热力学原理的随机优化算法,最早由 S. Kirkpatrick, C. D. Gelatt 和 M. P. Vecchi 于 1983 年提出。算法的灵感来自于固体物理学中的退火过程:通过加热和缓慢…...
同时用到,网页,java程序,数据库的web小应用
具体实现功能:通过网页传输添加用户的请求,需要通过JDBC来向 MySql 添加一个用户数据 第一步,部署所有需要用到的工具 IDEA(2021.1),Tomcat(9),谷歌浏览器,MySql,jdk(17) 第二步,创建java项目,提前部署数…...
星环科技推出语料开发工具TCS,重塑语料管理与应用新纪元
5月30-31日,2024向星力未来数据技术峰会期间,星环科技推出一款创新的语料开发工具——星环语料开发工具TCS(Transwarp Corpus Studio),旨在通过全面的语料生命周期管理,极大提升语料开发效率,助…...
【ARM】MDK安装ARM_compiler5无法打开安装程序
【更多软件使用问题请点击亿道电子官方网站】 1、 文档目标 在客户安装了最新版本的MDK5.37及后续更新版本,但原工程使用ARM_Compiler_5.06进行编译和调试,需安装ARM_Compiler_5.06的编译器版本,但在解压缩的过程中后续无法打开ARM_Compiler…...
PHP文字ocr识别接口示例、人工智能的发展
全球在人工智能升级的大背景下,有一定规模的制造商开始大量部署人工智能机器人、系统,以此取代危险、简单和重复性的工作。各种人工智能技术的迅猛发展,正在驱动各行业就业市场发现变革。 京东物流大家并不陌生,京东快递机器人在…...
基于ASP.NET+ SQL Server实现(Web)医院信息管理系统
医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上,开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识,在 vs 2017 平台上,进行 ASP.NET 应用程序和简易网站的开发;初步熟悉开发一…...
【网络安全产品大调研系列】2. 体验漏洞扫描
前言 2023 年漏洞扫描服务市场规模预计为 3.06(十亿美元)。漏洞扫描服务市场行业预计将从 2024 年的 3.48(十亿美元)增长到 2032 年的 9.54(十亿美元)。预测期内漏洞扫描服务市场 CAGR(增长率&…...
跨链模式:多链互操作架构与性能扩展方案
跨链模式:多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈:模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展(H2Cross架构): 适配层…...
12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...
有限自动机到正规文法转换器v1.0
1 项目简介 这是一个功能强大的有限自动机(Finite Automaton, FA)到正规文法(Regular Grammar)转换器,它配备了一个直观且完整的图形用户界面,使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列,以便知晓哪些列包含有价值的数据,…...
Golang——6、指针和结构体
指针和结构体 1、指针1.1、指针地址和指针类型1.2、指针取值1.3、new和make 2、结构体2.1、type关键字的使用2.2、结构体的定义和初始化2.3、结构体方法和接收者2.4、给任意类型添加方法2.5、结构体的匿名字段2.6、嵌套结构体2.7、嵌套匿名结构体2.8、结构体的继承 3、结构体与…...
TSN交换机正在重构工业网络,PROFINET和EtherCAT会被取代吗?
在工业自动化持续演进的今天,通信网络的角色正变得愈发关键。 2025年6月6日,为期三天的华南国际工业博览会在深圳国际会展中心(宝安)圆满落幕。作为国内工业通信领域的技术型企业,光路科技(Fiberroad&…...
DeepSeek源码深度解析 × 华为仓颉语言编程精粹——从MoE架构到全场景开发生态
前言 在人工智能技术飞速发展的今天,深度学习与大模型技术已成为推动行业变革的核心驱动力,而高效、灵活的开发工具与编程语言则为技术创新提供了重要支撑。本书以两大前沿技术领域为核心,系统性地呈现了两部深度技术著作的精华:…...
MySQL的pymysql操作
本章是MySQL的最后一章,MySQL到此完结,下一站Hadoop!!! 这章很简单,完整代码在最后,详细讲解之前python课程里面也有,感兴趣的可以往前找一下 一、查询操作 我们需要打开pycharm …...
