常见的垃圾回收算法原理及其模拟实现

news2025/7/15 22:30:51

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("refObj1
    var refObj2 = new RefCountedObject("refObj2
    refObj1.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)

《垃圾回收的算法与实现》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2387726.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

力扣面试150题--完全二叉树的节点个数

Day 51 题目描述 思路 根据完全二叉树的规律&#xff0c;完全二叉树的高度可以直接通过不断地访问左子树就可以获取&#xff0c;判断左右子树的高度: 1. 如果相等说明左子树是满二叉树, 然后进一步判断右子树的节点数(最后一层最后出现的节点必然在右子树中&#xff09; 2. 如…

BLIP3-o:一系列完全开源的统一多模态模型——架构、训练与数据集

摘要 在近期关于多模态模型的研究中&#xff0c;将图像理解与生成统一起来受到了越来越多的关注。尽管图像理解的设计选择已经得到了广泛研究&#xff0c;但对于具有图像生成功能的统一框架而言&#xff0c;其最优模型架构和训练方案仍有待进一步探索。鉴于自回归和扩散模型在…

DNS解析流程入门篇

一、DNS 解析流程 1.1 浏览器输入域名 当在浏览器中输入 www.baidu.com 时&#xff0c;操作系统会按照以下步骤进行 DNS 解析&#xff1a; 检查本地 hosts 文件 &#xff1a;操作系统先检查本地的 /etc/hosts 文件&#xff0c;查看是否存在域名与 IP 地址的对应关系。如果找到…

大模型系列22-MCP

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

【监控】Prometheus+Grafana 构建可视化监控

在云原生和微服务架构盛行的今天&#xff0c;监控系统已成为保障业务稳定性的核心基础设施。作为监控领域的标杆工具&#xff0c;Prometheus和Grafana凭借其高效的数据采集、灵活的可视化能力&#xff0c;成为运维和开发团队的“标配”。 一、Prometheus Prometheus诞生于2012…

RAGFlow源码安装操作过程

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

Unity使用XCharts动态配置数据——折线图(LineChart)

XCharts官网地址&#xff1a;https://xcharts-team.github.io/ 本地上传资源&#xff1a;https://download.csdn.net/download/m0_64375864/90919669 效果图&#xff1a; 动态配置数据&#xff1a; public class Test3 : MonoBehaviour {public LineChart lineChart;public …

【HITCSAPP 哈工大计算机系统期末大作业】 程序人生-Hello’s P2P

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

DAY9 热力图和箱线图的绘制

浙大疏锦行 学会了绘制两个图&#xff1a; 热力图&#xff1a;表示每个特征之间的影响&#xff0c;颜色越深数值越大表示这两个特征的关系越紧密 箱线图&#xff1a;表示每个特征的数据分布情况 箱体&#xff08;Box&#xff09;&#xff1a; 箱体的上下边界分别表示第一四分位…

解释一下NGINX的反向代理和正向代理的区别?

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

数学笔记一:标量、向量和矩阵基本概念辨析

一、标量 标量&#xff08;Scalar&#xff09; 是一种仅用数值大小&#xff08;即 “量值”&#xff09;就能完全描述的物理量或数学对象&#xff0c;它不具有方向属性。 例如在实数领域的正数、负数。 在物理学领域的多少斤、多少公斤、水温多少度、气温多少度都是标量。 …

vue3获取两个日期之间的所有时间

1.获取两个日期之间所有年月日 如图所示&#xff1a; 代码如下&#xff1a; <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. 用户从当前路径…

PXC集群

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

线程安全问题的成因

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

零基础远程连接课题组Linux服务器,安装anaconda,配置python环境(换源),在服务器上运行python代码【3/3 适合小白,步骤详细!!!】

远程连接服务器 请查阅之前的博客——零基础远程连接课题组Linux服务器&#xff0c;安装anaconda&#xff0c;配置python环境&#xff08;换源&#xff09;&#xff0c;在服务器上运行python代码【1/3 适合小白&#xff0c;步骤详细&#xff01;&#xff01;&#xff01;】&am…

unity实现wasd键控制汽车漫游

1.给汽车模型添加Box Collider和Rigidbody 2.创建脚本CarController并加载到汽车模型上 using UnityEngine; using UnityEngine.UI;public class CarController : MonoBehaviour

Python优雅执行SSH命令:10种方法+虚拟环境深度实践

引言&#xff1a;为什么选择Python操作SSH&#xff1f; SSH作为网络安全的基石&#xff0c;广泛应用于远程管理、文件传输和自动化任务。Python凭借其丰富的生态&#xff08;如paramiko、fabric&#xff09;和简洁语法&#xff0c;成为编写SSH脚本的首选语言。本文将系统梳理通…

嵌入式高级工程师面试全解:从 malloc 到 kernel panic 的系统知识梳理

在嵌入式和操作系统方向的技术面试中&#xff0c;常常会涉及一系列关于内存管理、虚拟化、系统权限、调试工具、外设通信等方面的问题。本文将基于一次真实的高级嵌入式工程师岗位面试问题&#xff0c;整理并详解所有相关技术点&#xff0c;作为一份结构清晰、知识全面的学习资…

C++(初阶)(二十)——封装实现set和map

二十&#xff0c;封装实现set和map 二十&#xff0c;封装实现set和map1&#xff0c;参数类型2&#xff0c;比较方式3&#xff0c;迭代器3.1&#xff0c;普通迭代器3.2&#xff0c;const迭代器3.3&#xff0c;set_map的迭代器实现 4&#xff0c;插入和查找5&#xff0c;特别的&a…