【JVM】(三) 深入理解JVM垃圾回收机制(GC)
文章目录
- 前言
- 一、死亡对象的判断方法
- 1.1 引用计数算法
- 1.2 可达性分析算法
- 二、垃圾回收算法
- 2.1 标记-清除算法
- 2.2 复制算法
- 2.3 标记-整理算法
- 2.5 分代算法
- 2.6 Minor GC 和 Major GC
前言
JVM 的垃圾回收机制(Garbage Collection)是 Java 中的重要特性之一,它负责在程序运行时自动回收不再使用的内存,以避免内存泄漏和提高程序的性能。垃圾回收机制的设计与实现对于 Java 程序的运行效率和稳定性至关重要。因此,作为一名合格的程序员,对于 JVM 的垃圾回收机制势必要有深入的了解,才能编写设计出合理高效的代码。
一、死亡对象的判断方法
在 JVM 的垃圾回收机制中,判断一个对象是否死亡是非常重要的,因为只有死亡的对象才能够被垃圾回收器回收,释放其占用的内存。其中常用的判断方法包括引用计数算法和可达性分析算法。
1.1 引用计数算法
引用计数算法是一种简单的垃圾回收算法,它的基本思想就是为每一个对象都维护一个引用计数器,用于记录当前有多少个引用指向该对象。当引用计数器变为零的时候,表示该对象没有任何引用指向它,因此就可以判断这个对象为死亡状态,就可以进行回收了。
引用计数的实现非常简单,判断效率也很高,在大部分情况下都是一个不错的算法。比如 Python 就是采用的这种方法实现对内存的管理。但是引用计数的最大问题就是循环引用。
例如下面的代码就展示了循环引用:
class Test{public Test test;
}public class Demo {public static void main(String[] args) {Test test1 = new Test();Test test2 = new Test();test1.test = test2;test2.test = test1;test1 = null;test2 = null;}
}
以上代码就是一个简单循环引用的案例,类Test中的test字段是一个引用类型,它指向另一个Test对象。在 main函数中,分别常见了两个Test对象,即 test1和test2,然后将其相互赋值给对方的 test字段。
如果是采用引用计数的方式,由于test1和test2相互引用,它们的引用计数器始终不为零,因为它们之间的引用数始终为1。根据引用计数算法的原理,即使这两个对象不再被程序使用,它们的引用计数器也不会变为零,因此不会被垃圾回收器回收。
1.2 可达性分析算法
为了解决循环引用的问题,JVM 采用了更为复杂的可达性分析算法。可达性分析算法的基本思想是以一组称为 GC Roots 的对象作为起始点,通过向下搜索和遍历对象应用链,判断对象是否可达(reachable)。
对象可达的含义:
- 一个对象是可达的,意味着它可以通过一系列引用链从
GC Roots对象到达。- 如果一个对象不可达,即它没有与任何
GC Roots对象相连接,那么该对象将被判断为死亡状态,即可以被垃圾回收器回收。
JVM 中的GC Roots对象包括:
- 已加载类的类对象(Class Object);
- 类静态变量引用的对象;
- 活动线程(Active Thread)的栈帧(Stack Frame);
JNI(Java Native Interface)中的本地方法栈(Native Method Stack)中引用的对象。
通过可达性分析算法,JVM 能够准确地判断对象的生命周期,避免了引用计数算法的循环引用问题,并有效地进行垃圾回收。
二、垃圾回收算法
垃圾回收算法是 JVM 垃圾回收机制的重要组成部分,它负责回收不再使用的对象,释放内存资源,确保程序的运行效率和稳定性。在 JVM 中,常见的垃圾回收算法包括:标记-清除算法、复制算法、标记-整理算法已经分代算法。
2.1 标记-清除算法
标记-清除算法是最基本的垃圾回收算法之一。其过程分为两个阶段:标记阶段和清除阶段。
- 标记阶段:从一组称为
GC Roots的对象出发,遍历所有可达对象,并将它们进行标记,表示这些对象是活动对象,不会被回收。 - 清除阶段:遍历整个堆空间,将没有标记的对象视为垃圾对象,直接回收这些垃圾对象的内存。回收后的内存形成一些不连续的碎片,可能会造成内存碎片化问题。

2.2 复制算法
复制算法是为了解决标记-清除算法的内存碎片化问题而设计的一种垃圾回收算法。它将堆空间划分为两个大小相等的区域,每次只使用其中的一半空间。其过程分为三个阶段:标记阶段、复制阶段和角色互换阶段。
- 标记阶段:与
标记-清除算法相同,从GC Roots对象出发,遍历所有可达对象,标记活动对象。 - 复制阶段:将所有活动对象从一个区域复制到另一个区域,使得复制后的内存是连续的,不会出现内存碎片化问题。
- 角色互换:完成复制后,两个区域的角色互换,原来的存活对象成为新的空闲区域,原来的空闲区域成为新的工作区域。

现在的商用虚拟机,包括
HotSpot都是采用这种收集算法来回收新生代区域的对象。
- 新生代中 98% 的对象都是
朝生夕死的,所以并不需要按照1 : 1的比例来划分内存空间,而是将内存(新生代内存)分为一块较大的Eden(伊甸园)空间和两块较小的Survivor(幸存者)空间。- 每次使用
Eden和其中一块Survivor(两个Survivor区域一个称为From(S0)区,另一个称为To (S1)区域)。- 当回收时,将
Eden和Survivor中还存活的对象一次性复制到另一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。- 当
Survivor空间不够用时,需要依赖其他内存(如老年代)进行分配担保。
关于HotSpot:
HotSpot默认Eden与Survivor的大小比例是 8 : 1,也就是说Eden : Survivor From : Survivor To = 8:1:1。所以每次新生代可用内存空间为整个新生代容量的 90%,而剩下的 10% 用来存放回收后存活的对象。
HotSpot是Java平台上使用最广泛的Java虚拟机(JVM)的一种实现。它由Oracle(前身是Sun Microsystems)开发,是Java >Development Kit(JDK)的一部分,也是OpenJDK的默认JVM实现之一。
HotSpot实现的复制算法流程如下:
- 当
Eden区满的时候,会触发第一次Minor GC,把还活着的对象拷贝到Survivor From区;当Eden区再次触发Minor GC的时候,会扫描Eden区和From区域,对两个区域进行垃圾回收,经过这次回收后还存活的对象,则直接复制到To区域,并将Eden和From区域清空。 - 当后续
Eden又发生Minor GC的时候,会对Eden和To区域进行垃圾回收,存活的对象复制到From区域,并将Eden和To区域清空。 - 部分对象会在
From和To区域中来回复制,如此交换15次(由 JVM 参数MaxTenuringThreshold决定,这个参数默认是15),最终如果还是存活的,就存入到老年代。

2.3 标记-整理算法
复制收集算法在对象存活率较高时会进行比较多的复制操作,效率会变低,因此在老年代一般不能使用复制算法。
针对老年代的特点,提出了标记-整理算法。标记过程仍与标记-清除过程一致,但后续步骤不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉端边界以外的内存。流程图如下:

2.5 分代算法
以上三种算法都存在一些共同的问题:
-
效率问题:
标记-清除算法和标记-整理算法需要对整个堆空间进行遍历,可能导致垃圾回收的效率较低。 -
内存碎片问题:
标记-清除算法在回收阶段会产生内存碎片,造成内存空间的浪费和不连续的内存布局。
而分代算法的诞生为了解决这些问题的,分代算法是一种综合利用多种垃圾回收算法的策略,通过对堆内存进行区域划分,针对不同区域采用不同的垃圾回收策略,从而实现更好的垃圾回收效果。
分代算法的设计思想:
在 Java 程序中,不同对象的生命周期往往是不同的。大部分新创建的对象很快就会变成垃圾,而一些对象可能会长时间存活。因此,根据这个特点就将堆划分为不同的代,分别处理不同生命周期的对象,这样就能更好地优化垃圾回收的效率和性能。
分代算法通常将堆内存划分为以下几个代:
-
新生代(Young Generation):新创建的对象通常被分配到新生代。新生代使用
复制算法进行垃圾回收,因为这些对象的生命周期短,产生的垃圾较多。 -
老年代(Old Generation):经过多次 GC 仍然存活的对象被移到老年代。老年代使用
标记-清除或标记-整理算法进行垃圾回收,因为这些对象的生命周期较长,产生的垃圾相对较少。 -
永久代(Permanent Generation):永久代用于存放类的元数据和常量等信息,在Java 8之后,永久代被元空间(Metaspace)所取代。
通过分代算法,不同代采用不同的垃圾回收策略,能够针对不同生命周期的对象进行更精细的垃圾回收,避免将整个堆空间都遍历,提高垃圾回收效率。这样的设计使得垃圾回收器能够根据应用程序的运行情况动态调整回收策略,从而更好地适应不同的应用场景。
2.6 Minor GC 和 Major GC
在 JVM 中,垃圾回收(Garbage Collection,GC)可以根据执行的任务不同而分为两种类型:即 Minor GC 和 Major GC(也称为 Full GC)。
Minor GC:
Minor GC是针对新生代的垃圾回收过程。新生代是 Java 堆内存中的一部分,用于存放刚被创建的对象。通常,新创建对象的生命周期较短,因此在新生代使用复制算法进行垃圾回收。
Minor GC的工作过程如下:
-
标记阶段:从
GC Roots对象出发,标记所有在新生代中存活的对象。 -
复制阶段:将所有存活的对象从
Eden区复制到Survivor区。 -
角色互换:完成复制后,
Eden区和Survivor区的角色互换,使得原来的Eden区成为新的空闲区,原来的Survivor区成为新的工作区。
Minor GC的目的是清理新生代中的垃圾对象,使得新生代能够为新的对象分配空间,尽量保证新生代的空间是连续的,避免产生内存碎片。
Major GC:
Major GC是针对老年区的垃圾回收过程,老年区用于存放长时间存活的对象。用于老年区中的对象生命周期较长,如果使用复制算法进行垃圾回收可能就会导致较大的复制成本,因此通常使用的是标记-清除或标记-整理算法。
Major GC的工作过程如下:
-
标记阶段:从
GC Roots对象出发,标记所有在老年代中存活的对象。 -
清除或整理阶段:根据采用的算法进行相应的垃圾回收操作。
标记-清除算法将清除未标记的垃圾对象,标记-整理算法将移动存活对象并清理未标记的垃圾对象。
Major GC的目的是清理老年代中的垃圾对象,以避免老年代占用过多内存资源,同时也是为了确保老年代的空间是连续的,避免内存碎片化问题。
相关文章:
【JVM】(三) 深入理解JVM垃圾回收机制(GC)
文章目录 前言一、死亡对象的判断方法1.1 引用计数算法1.2 可达性分析算法 二、垃圾回收算法2.1 标记-清除算法2.2 复制算法2.3 标记-整理算法2.5 分代算法2.6 Minor GC 和 Major GC 前言 JVM 的垃圾回收机制(Garbage Collection)是 Java 中的重要特性之…...
Flink CEP(二) 运行源码解析
通过DemoApp学习一下,CEP的源码执行逻辑。为下一篇实现CEP动态Pattern奠定理论基础。 1. Pattern的定义 Pattern<Tuple3<String, Long, String>,?> pattern Pattern.<Tuple3<String, Long, String>>begin("begin").where(new…...
剑指Offer-学习计划(四)双指针(下)
剑指 Offer 57. 和为s的两个数字 剑指 Offer 58 - I. 翻转单词顺序 剑指 Offer 21. 调整数组顺序使奇数位于偶数前面 题目一:调整数组顺序使奇数位于偶数前面 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数在数组的…...
深度学习——常见注意力机制
1.SENet SENet属于通道注意力机制。2017年提出,是imageNet最后的冠军 SENet采用的方法是对于特征层赋予权值。 重点在于如何赋权 1.将输入信息的所有通道平均池化。 2.平均池化后进行两次全连接,第一次全连接链接的神经元较少,第二次全连…...
Python 进阶(七):高级文件操作(shutil 模块)
❤️ 博客主页:水滴技术 🌸 订阅专栏:Python 入门核心技术 🚀 支持水滴:点赞👍 收藏⭐ 留言💬 文章目录 1. 简介2. 常用函数2.1 复制文件2.2 复制目录2.3 移动文件或目录2.4 删除文件或目录2.…...
保留网络:大型语言模型的Transformer继任者
原文信息 原文题目:《Retentive Network: A Successor to Transformer for Large Language Models》 原文引用:Sun Y, Dong L, Huang S, et al. Retentive Network: A Successor to Transformer for Large Language Models[J]. arXiv preprint arXiv:2…...
算法通关村第二关——反转链表青铜笔记
LeetCode 206.反转链表 建立虚拟结点辅助翻转 public ListNode reverseList(ListNode head) {ListNode ans new ListNode(-1);ListNode cur head;while(cur!null){ListNode curNext cur.next;cur.next ans.next;ans.next cur;cur curNext;}return ans.next; }不带虚拟头…...
【Linux】——线程安全
目录 关于线程进程的问题 可重入与线程安全 常见的线程安全的情况 常见的不可重入的情况 常见的可重入的情况 可重入与线程安全区别 可重入与线程安全联系 Linux线程互斥 进程线程间的互斥相关概念 互斥量mutex 互斥量mutex常用接口 互斥量改造抢票系统 互斥量的原…...
[React]生命周期
前言 学习React,生命周期很重要,我们了解完生命周期的各个组件,对写高性能组件会有很大的帮助. Ract生命周期 React 生命周期分为三种状态 1. 初始化 2.更新 3.销毁 初始化 1、getDefaultProps() 设置默认的props,也可以用duf…...
【2023】Redis实现消息队列的方式汇总以及代码实现
Redis实现消息队列的方式汇总以及代码实现 前言开始前准备1、添加依赖2、添加配置的Bean 具体实现一、从最简单的开始:List 队列代码实现 二、发布订阅模式:Pub/Sub1、使用RedisMessageListenerContainer实现订阅2、还可以使用redisTemplate实现订阅 三、…...
ARM裸机-10
1、X210开发板和光盘资料 1.1、配置信息 CPU:三星S5PV210 内存:512M DDR2 SDRAM Flash:4GB iBand LCD:7寸,分辨率800x480 触摸屏:电容触摸屏 2、X210开发板硬件手册 3、X210开发板刷系统 3.1、什么是刷…...
「C/C++」C/C++指针详解
✨博客主页何曾参静谧的博客📌文章专栏「C/C」C/C程序设计📚全部专栏「UG/NX」NX二次开发「UG/NX」BlockUI集合「VS」Visual Studio「QT」QT5程序设计「C/C」C/C程序设计「Win」Windows程序设计「算法」数据结构与算法「File」数据文件格式 目录 一、术语…...
提高电脑寿命的维护技巧与方法分享
在维护电脑运行方面,我有一些自己觉得非常有用的技巧和方法。下面我将分享一些我常用的维护技巧,并解释为什么我会选择这样做以及这样做的好处。 首先,我经常清理我的电脑内部的灰尘。电脑内部的灰尘会影响散热效果,导致电脑发热…...
React常见面试题
React常见面试题 一、React中的样式管理有哪些方法 内联样式:对象,作用于当前组件普通样式表: 作用于全局,文件名是:xxx.scssCSS模块:类似Vue的scoped, 文件名需是:xxx.module.scs…...
C++中数据的输入输出介绍
C中数据的输入输出介绍 C中数据的输入输出涉及到的文件 <iostream>:这是C标准库中最常用的头文件之一,包含了进行标准输入输出操作的类和对象,如std::cin、std::cout、std::endl等。 <iomanip>:该头文件提供了一些用…...
0101日志-运维-mysql
1 错误日志 错误日志(Error Log):错误日志记录了MySQL引擎在运行过程中出现的错误和异常情况。这些错误可能包括启动和关闭问题、数据库崩溃、权限问题等。错误日志对于排查和解决MySQL引擎问题非常有帮助。 改日志默认开启,默认存…...
LabVIEW使用灰度和边缘检测进行视频滤波
LabVIEW使用灰度和边缘检测进行视频滤波 数字图像处理(DIP)是真实和连续世界的离散表示。除此之外,这种数字图像在通信、医学、遥感、地震学、工业自动化、机器人、航空航天和教育等领域变得非常重要。计算机技术越来越需要视频图像的数字图…...
SpringBoot整合WebService
SpringBoot整合WebService WebService是一个比较旧的远程调用通信框架,现在企业项目中用的比较少,因为它逐步被SpringCloud所取代,它的优势就是能够跨语言平台通信,所以还有点价值,下面来看看如何在SpringBoot项目中使…...
【LangChain】向量存储之FAISS
LangChain学习文档 【LangChain】向量存储(Vector stores)【LangChain】向量存储之FAISS 概要 Facebook AI 相似性搜索(Faiss)是一个用于高效相似性搜索和密集向量聚类的库。它包含的算法可以搜索任意大小的向量集,甚至可能无法容纳在 RAM 中…...
小研究 - 主动式微服务细粒度弹性缩放算法研究(三)
微服务架构已成为云数据中心的基本服务架构。但目前关于微服务系统弹性缩放的研究大多是基于服务或实例级别的水平缩放,忽略了能够充分利用单台服务器资源的细粒度垂直缩放,从而导致资源浪费。为此,本文设计了主动式微服务细粒度弹性缩放算法…...
HY-Motion 1.0作品集展示:12类日常动作+8类专业运动生成效果
HY-Motion 1.0作品集展示:12类日常动作8类专业运动生成效果 1. 引言:当文字能驱动骨骼 想象一下,你正在为一个游戏角色设计一套连贯的格斗动作,或者为一个虚拟主播编排一段自然的舞蹈。传统流程需要动画师一帧一帧地调整骨骼&am…...
ChatGPT+RMBG-2.0:智能图像处理工作流自动化
ChatGPTRMBG-2.0:智能图像处理工作流自动化 1. 当你还在手动抠图时,有人已经用一句话完成整套流程 上周帮朋友处理一批电商产品图,他花了整整两天时间在Photoshop里一张张抠背景、调边缘、换底色。最后发来消息说:“要是能对着图…...
CPython AOT编译器模块全图谱,从_pycompile.c到aot_codegen.cc的17个关键函数逐行注释与性能拐点分析
第一章:CPython AOT编译器模块全图谱概览与演进脉络CPython 的 Ahead-of-Time(AOT)编译能力并非原生内建,而是近年来通过社区驱动的实验性项目逐步构建起模块化支撑体系。其核心演进路径始于 PEP 698 提出的字节码预编译增强机制&…...
RT thread—iic—at24c04读写操作
at24c04介绍:存储容量:4 Kbits(即 512 字节)。内部结构为 32 页,每页 16 字节。地址0x000-0x1FF通信接口:标准 I2C(时钟线 SCL 和数据线 SDA),支持最高 400 kHz 的快速模…...
RK3576开发板调试EC11编码器,一分钟就失灵?原来是XL9535芯片这个引脚没上拉
RK3576开发板EC11编码器调试:XL9535中断引脚上拉缺失引发的"一分钟失灵"之谜 刚拿到RK3576开发板时,我满心期待地接上了EC11旋转编码器进行测试——上电后旋转旋钮,系统响应灵敏,GPIO中断触发准确。但正当我准备庆祝调试…...
彩灯广告屏PLC控制S7-200程序:包含梯形图、接线图、原理图及IO分配与组态画面详解
彩灯广告屏的PLC控制S7-200程序 程序 我们主要的后发送的产品有,带解释的梯形图接线图原理图图纸,io分配,组态画面上周刚帮客户搞定了一套户外彩灯广告屏的PLC控制项目,用的还是经典的S7-200,本来以为老架构玩不出花…...
GLM-4.1V-9B-Base实战教程:跨境电商A+页面图像卖点自动提炼
GLM-4.1V-9B-Base实战教程:跨境电商A页面图像卖点自动提炼 1. 为什么需要自动提炼图像卖点 跨境电商卖家每天需要处理大量商品图片,传统人工标注方式存在三个痛点: 效率低下:一个运营人员每天最多处理50-100张图片成本高昂&…...
MotorController:嵌入式伺服电机驱动的确定性执行封装
1. 项目概述MotorController是一个面向伺服系统电机控制的轻量级工具类,其设计目标并非替代完整的运动控制固件栈,而是为嵌入式工程师提供一套可直接集成、低侵入、高可控性的底层电机驱动封装。该类不依赖特定硬件抽象层(HAL)或实…...
从‘迷失’到‘秒达’:我用PyCharm的‘符号搜索’和‘调用链查看’重构了老项目
从‘迷失’到‘秒达’:我用PyCharm的‘符号搜索’和‘调用链查看’重构了老项目 接手一个缺乏文档的遗留代码库,就像被扔进一座没有地图的迷宫。上周我面对的就是这样一个Python项目——3万行代码,零文档,函数命名随意得像临时起意…...
从YOLOv8到RTDETR:如何将训练后的YOLO指标无缝转换为COCO格式
1. 为什么需要YOLO到COCO格式转换 当你用YOLOv8官方代码训练RTDETR模型时,会发现评估结果默认输出的是YOLO格式指标。但学术界和工业界普遍采用COCO评估标准,这就好比在中国用人民币交易,到了欧洲就得换成欧元。我在去年帮某无人机公司做目标…...

