常见的垃圾回收算法原理及其模拟实现
1.标记 - 清除(Mark - Sweep)算法:
这是一种基础的垃圾回收算法。首先标记所有可达的对象,然后清除未被标记的对象。
缺点是会产生内存碎片。
原理:
如下图分配一段内存,假设已经存储上数据了
标记所有可达对象
清除未标记的对象,然后重置标记
模拟实现代码:
实现GCObject类:
internal class GCObject{private List<GCObject> referencesObjs;public bool Marked { get; set; }public List<GCObject> ReferencesObjs { get => this.referencesObjs; }public string Name { get; private set; }public GCObject(string name) {referencesObjs = new List<GCObject>();Name = name;}public void AddReference(GCObject obj){referencesObjs.Add(obj);}}
实现GC回收类:
internal class MarkSweepGC{private List<GCObject> _heap;public MarkSweepGC(){_heap = new List<GCObject>();}// 分配新对象到堆public GCObject CreateObject(string name){var obj = new GCObject(name);_heap.Add(obj);return obj;}public void Collect(params GCObject[] roots){// 标记阶段void Mark(GCObject obj){ if (!obj.Marked){obj.Marked = true;foreach (var child in obj.ReferencesObjs) Mark(child);}}foreach (var root in roots) Mark(root);// 清除阶段_heap.RemoveAll(obj =>{if (!obj.Marked && !obj.Name.Equals("root")) Console.WriteLine($"回收 {obj.GetHashCode()},{obj.Name}");return !obj.Marked;});// 重置标记_heap.ForEach(obj => obj.Marked = false);}}
实现测试类:创建一些内存节点,将gc2及其子节点标记
/// <summary>
/// 标记清除算法测试
/// </summary>
static void MarkSweepGCTest()
{var gcCollector = new MarkSweepGC();GCObject root = gcCollector.CreateObject("root");GCObject gc2 = gcCollector.CreateObject("gc2");GCObject gc3 = gcCollector.CreateObject("gc3");GCObject gc4 = gcCollector.CreateObject("gc4");root.AddReference(gc2);gc2.AddReference(gc3);gc2.AddReference(gc4);GCObject gc5 = gcCollector.CreateObject("gc5");GCObject gc6 = gcCollector.CreateObject("gc6");GCObject gc7 = gcCollector.CreateObject("gc7");root.AddReference(gc5);gc5.AddReference(gc6);gc5.AddReference(gc7);gcCollector.Collect(gc2);
}
结果:
2.复制(Copying)算法:
将内存分为两块,每次只使用其中一块。当进行垃圾回收时,将存活对象复制到另一块内存中,然后清空原来的内存块。
优点是不会产生内存碎片,缺点是内存使用率只有 50%。
原理:
将内存分成两块,假设其中已经存储上数据了
将存活的对象标记
将存活对象和根对象赋值到target内存区 ,并重置标记
模拟实现代码:
GCObject类:同上
CopyingGC类:
internal class CopyingGC{private List<GCObject> _fromSpace;private List<GCObject> _toSpace;private bool _isFromActive = true;public CopyingGC() { _fromSpace = new List<GCObject>();_toSpace = new List<GCObject>();}// 分配对象到当前活动区public GCObject CreateObject(string name){var obj = new GCObject(name);(_isFromActive ? _fromSpace : _toSpace).Add(obj);return obj;}public void Collect(params GCObject[] roots){var active = _isFromActive ? _fromSpace : _toSpace;var target = _isFromActive ? _toSpace : _fromSpace;target.Clear();var marked = new HashSet<GCObject>();void Mark(GCObject obj){if (obj == null || marked.Contains(obj)) return;marked.Add(obj);foreach (var child in obj.ReferencesObjs) Mark(child);}foreach (var root in roots) Mark(root);// 复制存活对象到目标区foreach (var obj in active){if (marked.Contains(obj) || obj.Name.Equals("root")) target.Add(obj);}// 交换区域_isFromActive = !_isFromActive;Console.WriteLine($"GC完成,存活对象数:{target.Count}");foreach (var child in target){Console.WriteLine($"存活对象: {child.GetHashCode()},{child.Name}");}}}
CopyingGCTest类:
/// <summary>
/// 复制算法测试
/// </summary>
static void CopyingGCTest()
{var gcCollector = new CopyingGC();GCObject root = gcCollector.CreateObject("root");GCObject gc2 = gcCollector.CreateObject("gc2");GCObject gc3 = gcCollector.CreateObject("gc3");GCObject gc4 = gcCollector.CreateObject("gc4");root.AddReference(gc2);gc2.AddReference(gc3);gc2.AddReference(gc4);GCObject gc5 = gcCollector.CreateObject("gc5");GCObject gc6 = gcCollector.CreateObject("gc6");GCObject gc7 = gcCollector.CreateObject("gc7");root.AddReference(gc5);gc5.AddReference(gc6);gc5.AddReference(gc7);gcCollector.Collect(gc2);
}
结果:
3.标记 - 压缩(Mark - Compact)算法:
标记阶段与标记 - 清除算法相同,在清除阶段会将所有存活对象移动到一端,然后清理另一端的内存。
解决了内存碎片问题,同时避免了复制算法的内存浪费。
原理:
分配一段内存,假设已经存储上数据了
标记可达对象
移动对象
清除未标记对象
重置标记
模拟实现代码:
GCObject类:同上
MarkCompactGC类:
internal class MarkCompactGC{private List<GCObject> _heap;public MarkCompactGC(){_heap = new List<GCObject>();}// 分配新对象到堆public GCObject CreateObject(string name){var obj = new GCObject(name);_heap.Add(obj);return obj;}public void Collect(params GCObject[] roots){// 标记阶段void Mark(GCObject obj){if (!obj.Marked){obj.Marked = true;foreach (var child in obj.ReferencesObjs) Mark(child);}}foreach (var root in roots) Mark(root);// 压缩阶段int newAddress = 0;for (int i = 0; i < _heap.Count; i++){if (_heap[i].Marked || _heap[i].Name.Equals("root")){// 移动对象到新位置并更新引用_heap[newAddress] = _heap[i];newAddress++;}}_heap.RemoveRange(newAddress, _heap.Count - newAddress);foreach (var child in _heap){Console.WriteLine($"存活对象: {child.GetHashCode()},{child.Name}");}}}
测试代码:
/// <summary>/// 标记压缩算法测试/// </summary>static void MarkCompactGC(){var gcCollector = new MarkCompactGC();GCObject root = gcCollector.CreateObject("root");GCObject gc2 = gcCollector.CreateObject("gc2");GCObject gc3 = gcCollector.CreateObject("gc3");GCObject gc4 = gcCollector.CreateObject("gc4");root.AddReference(gc2);gc2.AddReference(gc3);gc2.AddReference(gc4);GCObject gc5 = gcCollector.CreateObject("gc5");GCObject gc6 = gcCollector.CreateObject("gc6");GCObject gc7 = gcCollector.CreateObject("gc7");root.AddReference(gc5);gc5.AddReference(gc6);gc5.AddReference(gc7);gcCollector.Collect(gc2);}
结果:
4.引用计数(Reference Counting)算法:
为每个对象维护一个引用计数器,当有新的引用指向对象时计数器加 1,引用失效时计数器减 1。当计数器为 0 时,对象被回收。
缺点是无法处理循环引用的情况。
原理:
分配一块内存,假设已经存储上数据了
分配的每块内存都有一个计数
如果引用计数归0,则就释放这块内存
模拟实现代码:
RefCountObject类:
internal class RefCountedObject
{private int _refCount = 0;private List<RefCountedObject> _children;private List<WeakReference<RefCountedObject>> _weakRefs; // 弱引用容器private string _name;public RefCountedObject(string name){_children = new List<RefCountedObject>();_weakRefs = new List<WeakReference<RefCountedObject>>();_name = name;}/// <summary>/// 添加强引用/// </summary>public void AddRef(){if (_refCount == int.MaxValue) return;_refCount++;}/// <summary>/// 添加弱引用(用于解决循环引用)/// </summary>public void AddWeakRef(RefCountedObject target){var weakRef = new WeakReference<RefCountedObject>(target);_weakRefs.Add(weakRef);}/// <summary>/// 释放引用/// </summary>public void Release(){if (_refCount <= 0) return;_refCount--;if (_refCount == 0){Dispose();}}/// <summary>/// 添加子对象(自动管理引用计数)/// </summary>public void AddChild(RefCountedObject child){if (child == null) return;child.AddRef();_children.Add(child);}public void Dispose(){// 释放强引用子对象foreach (var child in _children){child.Release();}_children.Clear();// 释放弱引用(不操作引用计数)_weakRefs.Clear();// 释放非托管资源ReleaseResources();}protected virtual void ReleaseResources(){Console.WriteLine($"{this.ToString()} 释放资源");}public override string ToString(){return $"[{this._name}@{GetHashCode():x4}]";}
}
测试:
/// <summary>
/// 引用计数算法测试
/// </summary>
static void RefCountedGCTest()
{//正常引用var refObj1 = new RefCountedObject("refObj1var refObj2 = new RefCountedObject("refObj2refObj1.AddRef(); //根引用refObj1.AddChild(refObj2); //weapon 引用+1//手动释放原始引用refObj2.Release();refObj1.Release();//循环引用(使用弱引用解决)var nodeA = new RefCountedObject("节点A");var nodeB = new RefCountedObject("节点B");nodeA.AddRef(); // 根引用// 相互引用(A强引用B,B弱引用A)nodeA.AddChild(nodeB);nodeB.AddWeakRef(nodeA); // 使用弱引用避免循//nodeB.AddChild(nodeA);// 释放根引用nodeA.Release(); // 正确释放整个引用链
}
结果:
5.分代收集(Generational Collection)算法:
将对象按照生命周期分为不同的代(如新生代、老生代),不同代采用不同的回收策略。
通常新生代使用复制算法,老生代使用标记 - 清除或标记 - 压缩算法。
原理:
将对象分不同代,以分成三代为例
在new新对象时,检查第0代内存是否已达上限,如果是触发第0代GC,
标记复制,存活对象晋升到第1代
清除掉没有被标记的
如果第1代内存达到上限,触发第1代GC,将存活对象晋升到第2代,和第0代GC处理类似。
在第2代内存达到上限后,触发第2代GC,将全堆执行GC。
模拟实现代码:
GCObject类:
internal class GCObject{private List<GCObject> referencesObjs;public int Generation { get; set; }public List<GCObject> ReferencesObjs { get => this.referencesObjs; }public string Name { get; private set; }public GCObject(string name,int generation = 0){referencesObjs = new List<GCObject>();Name = name;Generation = generation;}public void AddReference(GCObject obj){referencesObjs.Add(obj);}}
GenerationalGC类:
internal class GenerationalGC
{private readonly List<GCObject>[] _generations;private readonly HashSet<GCObject> _marked;public GenerationalGC(){_generations = new List<GCObject>[3];_marked = new HashSet<GCObject>();for (int i = 0; i < 3; i++)_generations[i] = new List<GCObject>();}// 分配新对象(默认进入0代)public GCObject NewObject(string name , int generation = 0){var obj = new GCObject(name,generation);_generations[generation].Add(obj);return obj;}// 执行分代回收//这里将可标记对象传进来,模拟可达对象public void Collect(int generation, params GCObject[] roots){Console.WriteLine($"\n=== 执行第{generation}代GC ===");// 标记阶段(递归标记可达对象)void Mark(GCObject obj){if (obj == null || _marked.Contains(obj)) return;_marked.Add(obj);foreach (var child in obj.ReferencesObjs) Mark(child);}// 从根出发(实际应包含全局/栈引用)foreach (var root in roots) Mark(root);// 清除和晋升for (int gen = 0; gen <= generation; gen++){var toRemove = new List<GCObject>();foreach (var obj in _generations[gen]){if (!_marked.Contains(obj)){Console.WriteLine($"回收 {obj.GetHashCode():x4} (代{gen})");}else if (gen < 2) // 晋升存活对象{obj.Generation = gen + 1;_generations[gen + 1].Add(obj);}toRemove.Add(obj);}_generations[gen].RemoveAll(toRemove.Contains);}_marked.Clear();}public int GetGenerationCount(int idx){return _generations[idx].Count;}
}
测试代码:
/// <summary>/// 分代垃圾回收算法测试/// </summary>static void GenerationalGCTest(){var gc = new GenerationalGC();// 创建对象并建立引用var obj1 = gc.NewObject("obj1");var obj2 = gc.NewObject("obj2");obj1.AddReference(obj2);var obj3 = gc.NewObject("obj3");var obj4 = gc.NewObject("obj4");// 执行第0代GC(回收无引用的对象)gc.Collect(0,obj1);// 查看对象分布Console.WriteLine("\n=== 每代对象分布 ===");for (int gen = 0; gen < 3; gen++){Console.WriteLine($"代{gen}: {gc.GetGenerationCount(gen)} 对象");}// 创建新对象for (int i = 0; i < 5; i++){var temp = gc.NewObject("oldGCObj" + i);if (i % 2 == 0)obj2.AddReference(temp); // 建立引用}// 查看对象分布Console.WriteLine("\n=== 每代对象分布 ===");for (int gen = 0; gen < 3; gen++){Console.WriteLine($"代{gen}: {gc.GetGenerationCount(gen)} 对象");}// 模拟多次GC触发晋升gc.Collect(0,obj1);// 查看对象分布Console.WriteLine("\n=== 每代对象分布 ===");for (int gen = 0; gen < 3; gen++){Console.WriteLine($"代{gen}: {gc.GetGenerationCount(gen)} 对象");}gc.Collect(0, obj1);// 查看对象分布Console.WriteLine("\n=== 每代对象分布 ===");for (int gen = 0; gen < 3; gen++){Console.WriteLine($"代{gen}: {gc.GetGenerationCount(gen)} 对象");}gc.Collect(1,obj1); // 显式回收第1代// 查看对象分布Console.WriteLine("\n=== 最终代分布 ===");for (int gen = 0; gen < 3; gen++){Console.WriteLine($"代{gen}: {gc.GetGenerationCount(gen)} 对象");}}
结果:
链接:
标记和清除垃圾回收算法 - YouTube
Garbage Collection in C# .NetCore: Explained! (youtube.com)
Garbage Collection Process | Garbage Collector | Automatic Memory Management | .Net Framework (youtube.com)
《垃圾回收的算法与实现》
相关文章:

常见的垃圾回收算法原理及其模拟实现
1.标记 - 清除(Mark - Sweep)算法: 这是一种基础的垃圾回收算法。首先标记所有可达的对象,然后清除未被标记的对象。 缺点是会产生内存碎片。 原理: 如下图分配一段内存,假设已经存储上数据了 标记所有…...
fpga-编程线性序列机和状态机
一、线性序列机和有限状态机和(状态机-编程思想)的原理 序列机是什么:用计数器对时钟个数计数,根据相应时钟周期下的单个周期时间和计数个数可以确定某个时刻的时间,确定时间后再需要时间点转换电平! 采用…...

力扣面试150题--完全二叉树的节点个数
Day 51 题目描述 思路 根据完全二叉树的规律,完全二叉树的高度可以直接通过不断地访问左子树就可以获取,判断左右子树的高度: 1. 如果相等说明左子树是满二叉树, 然后进一步判断右子树的节点数(最后一层最后出现的节点必然在右子树中) 2. 如…...
Qt 多线程环境下的全局变量管理与密码安全
在现代软件开发中,全局变量的管理和敏感信息的保护是两个重要的课题。特别是在多线程环境中,不正确的全局变量使用可能导致数据竞争和不一致的问题,而密码等敏感信息的明文存储更是会带来严重的安全隐患。本文将介绍如何在 Qt 框架下实现一个…...
内网映射有什么作用,如何实现内网的网络地址映射到公网连接?
在网络环境中,内网映射是一项重要的技术,它允许用户通过外部网络访问位于内部网络中的设备或服务。如自己电脑上的程序提供他人使用,或在家远程管理公司办公OA等涉及不同网络间的通信和数据交互。nat123作为一款老牌的内网映射工具࿰…...

BLIP3-o:一系列完全开源的统一多模态模型——架构、训练与数据集
摘要 在近期关于多模态模型的研究中,将图像理解与生成统一起来受到了越来越多的关注。尽管图像理解的设计选择已经得到了广泛研究,但对于具有图像生成功能的统一框架而言,其最优模型架构和训练方案仍有待进一步探索。鉴于自回归和扩散模型在…...

DNS解析流程入门篇
一、DNS 解析流程 1.1 浏览器输入域名 当在浏览器中输入 www.baidu.com 时,操作系统会按照以下步骤进行 DNS 解析: 检查本地 hosts 文件 :操作系统先检查本地的 /etc/hosts 文件,查看是否存在域名与 IP 地址的对应关系。如果找到…...
spring4第2课-ioc控制反转-依赖注入,是为了解决耦合问题
继续学习ioc控制反转, IOC(Inversion of Control)控制反转,也叫依赖注入, 目的是解决程序的耦合问题,轻量级spring的核心。 1.定义bean.xml <?xml version"1.0" encoding"UTF-8"…...

大模型系列22-MCP
大模型系列22-MCP 玩转 MCP 协议:用 Cline DeepSeek 接入天气服务什么是 MCP?环境准备:VScode Cline DeepSeek**配置 DeepSeek 模型:****配置 MCP 工具****uvx是什么?****安装 uv(会自动有 uvx 命令&…...

【监控】Prometheus+Grafana 构建可视化监控
在云原生和微服务架构盛行的今天,监控系统已成为保障业务稳定性的核心基础设施。作为监控领域的标杆工具,Prometheus和Grafana凭借其高效的数据采集、灵活的可视化能力,成为运维和开发团队的“标配”。 一、Prometheus Prometheus诞生于2012…...
vscode里几种程序调试配置
标题调试python嵌入的c代码,例如 import torch from torch.utils.cpp_extension import loadtest_load load(nametest_load, sources[test.cpp],extra_cflags[-O0, -g],#extra_cflags[-O1],verboseTrue, ) a torch.tensor([1, 2, 3]) b torch.tensor([4, 5, 6]) result te…...

RAGFlow源码安装操作过程
RAGFlow是一款基于深度文档理解构建的开源 RAG(Retrieval-Augmented Generation)引擎,可作为Dify的外部知识库使用[1]。本文主要介绍RAGFlow前端和后端等源码安装操作过程。 一.后端安装 特别注意:python ">3.12,<3…...

Unity使用XCharts动态配置数据——折线图(LineChart)
XCharts官网地址:https://xcharts-team.github.io/ 本地上传资源:https://download.csdn.net/download/m0_64375864/90919669 效果图: 动态配置数据: public class Test3 : MonoBehaviour {public LineChart lineChart;public …...

【HITCSAPP 哈工大计算机系统期末大作业】 程序人生-Hello’s P2P
计算机系统 大作业 题 目 程序人生-Hello’s P2P 专 业 计算机与电子通信类 学 号 2023112915 班 级 23L0505 学 生 杨昕彦 指 导 教 师 刘宏伟 计算机科学…...

DAY9 热力图和箱线图的绘制
浙大疏锦行 学会了绘制两个图: 热力图:表示每个特征之间的影响,颜色越深数值越大表示这两个特征的关系越紧密 箱线图:表示每个特征的数据分布情况 箱体(Box): 箱体的上下边界分别表示第一四分位…...
如何查看 GitLab 内置的 PostgreSQL 版本?
GitLab 依赖 PostgreSQL,PostgreSQL 的升级会随着 GitLab 的版本升级而进行,本文分享查看 GitLab 内置 PostgreSQL 版本的方法。 GitLab 版本和 PostgreSQL 版本需要一一对应,默认情况下使用 Omnibus 方式安装的 GitLab 实例会自动升级 Postg…...
VR 技术与病毒分离鉴定:一场奇妙的邂逅
过去,病毒分离鉴定主要依靠传统实验技术,虽为病毒学发展奠定基础,但在现代病毒研究中有诸多局限。 沉浸式操作,告别风险担忧 VR 技术给病毒分离鉴定带来的最大变革是大幅提升实验安全性。借助 VR 设备,实验者身处高…...

解释一下NGINX的反向代理和正向代理的区别?
大家好,我是锋哥。今天分享关于【解释一下NGINX的反向代理和正向代理的区别?】面试题。希望对大家有帮助; 解释一下NGINX的反向代理和正向代理的区别? NGINX的反向代理和正向代理的区别主要体现在它们的功能和使用场景上。下面我会详细解释它们的定义…...

数学笔记一:标量、向量和矩阵基本概念辨析
一、标量 标量(Scalar) 是一种仅用数值大小(即 “量值”)就能完全描述的物理量或数学对象,它不具有方向属性。 例如在实数领域的正数、负数。 在物理学领域的多少斤、多少公斤、水温多少度、气温多少度都是标量。 …...

vue3获取两个日期之间的所有时间
1.获取两个日期之间所有年月日 如图所示: 代码如下: <template><div class"datePicker"><el-date-pickerv-model"value1"type"daterange"range-separator"至"start-placeholder"开始时间…...

Python 实现简易版的文件管理(结合网络编程)
目录 一、Python 代码实现1. 服务器端2. 客户端 二、结果展示1. 查看当前路径下的内容 ls2. 切换当前路径 cd3. 查看当前路径 pwd4. 显示根目录下的树状结构 tree5. 在当前路径下创建目录 mkdir6. 删除当前路径下的文件或目录 rm7. 复制文件 mv8. 移动文件 cp9. 用户从当前路径…...
元组可以比较大小吗?一次返回多个值?编程语言的元组?声明变量一定需要指定类型吗?
目录 元组可以比较大小吗? 一次返回多个值? 编程语言的元组 支持元组的语言 元组的基本特性 元组的初始化和使用 声明变量一定需要指定类型吗? var类型 元组可以比较大小吗? 不同编程语言对元组的定位稍有差异,是否可以比较大小随语言而定。 Swift支持…...

PXC集群
PXC集群 一、环境介绍二、PXC安装1、关闭默认mysql模块2、安装yum源3、准备pxc安装环境4、安装pxc5、启动mysql,并更改root密码 三、搭建PXC集群1、编辑/etc/my.cnf 配置文件(1)pxc1节点配置文件(2)pxc2节点配置文件&a…...

线程安全问题的成因
前言 大家晚上好呀~~ 今天学习了线程不安全问题的成因。线程安全问题是十分重要的知识点,我想把我所学的与大家分享一波,希望可以帮助到有需要的人,同时加深自己对于线程安全问题的理解。 分析过程如下 结语 今天心情还不错~ 要坚持持续…...

零基础远程连接课题组Linux服务器,安装anaconda,配置python环境(换源),在服务器上运行python代码【3/3 适合小白,步骤详细!!!】
远程连接服务器 请查阅之前的博客——零基础远程连接课题组Linux服务器,安装anaconda,配置python环境(换源),在服务器上运行python代码【1/3 适合小白,步骤详细!!!】&am…...
字节跳动BAGEL-7B-MoT模型开源:多模态AI技术的新范式与行业涟漪
在人工智能领域,技术开源与商业化落地的平衡始终是核心议题。2025年5月26日,字节跳动发布开源多模态AI模型BAGEL-7B-MoT,凭借其混合架构设计与跨模态处理能力,在图像生成、视觉理解等任务中展现出与GPT-4o等闭源模型抗衡的实力。这…...
Ubuntu静态IP配置信息查看命令
Ubuntu静态IP配置信息查看命令 1. 查看当前IP地址信息 (Address & Netmask) 方法1: 使用ip命令 (推荐) ip addr show # 或简写 ip a方法2: 使用ifconfig命令 ifconfig # 查看特定网卡 ifconfig eth0方法3: 只查看IP地址 hostname -I2. 查看网关信息 (Gateway) 查看默…...

unity实现wasd键控制汽车漫游
1.给汽车模型添加Box Collider和Rigidbody 2.创建脚本CarController并加载到汽车模型上 using UnityEngine; using UnityEngine.UI;public class CarController : MonoBehaviour...

Python优雅执行SSH命令:10种方法+虚拟环境深度实践
引言:为什么选择Python操作SSH? SSH作为网络安全的基石,广泛应用于远程管理、文件传输和自动化任务。Python凭借其丰富的生态(如paramiko、fabric)和简洁语法,成为编写SSH脚本的首选语言。本文将系统梳理通…...
Linux TCP与Socket与IO多路复用(Epoll)
目录 一、背景 二、交互流程 2.1 数据流动 2.2 对象之间的关系 三、TCP 3.1 为什么需要三次握手 3.2 三次握手流程 3.3 三次握手后的产物 3.4 TCB 四、Socket 4.1 Java Socket和C Socket 4.2 Socket的本质 4.3 Socket和TCB的关系 4.4 通过文件描述符调用Socket的…...