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

1.标记 - 清除(Mark - Sweep)算法:

这是一种基础的垃圾回收算法。首先标记所有可达的对象,然后清除未被标记的对象

缺点是会产生内存碎片

原理:

如下图分配一段内存,假设已经存储上数据了

标记所有可达对象
清除未标记的对象,然后重置标记

模拟实现代码:

实现GCObject类:

cs 复制代码
 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回收类:

cs 复制代码
   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及其子节点标记

cs 复制代码
/// <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类:

cs 复制代码
 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类:

cs 复制代码
/// <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类:

cs 复制代码
    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}");
            }
        }
    }

测试代码:

cs 复制代码
   /// <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类:

cs 复制代码
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}]";
    }
}

测试:

cs 复制代码
/// <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类:

cs 复制代码
  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类:

cs 复制代码
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;
    }
}

测试代码:

cs 复制代码
  /// <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)

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

相关推荐
CoderIsArt8 小时前
自动加工脚本程序变量管理器
c#
unicrom_深圳市由你创科技14 小时前
C#与 Prism 框架:构建模块化的 WPF 应用程序
开发语言·c#·wpf
钢铁男儿14 小时前
C#核心概念解析:析构函数、readonly与this关键字
开发语言·javascript·c#
拜特流动15 小时前
C# Socket对象创建方式详解
网络·c#
JuneXcy16 小时前
班级管理系统
c#
Kookoos17 小时前
ABP VNext + CRDT 打造实时协同编辑
c#·.net·yjs·crdt·abp vnext
o0向阳而生0o19 小时前
54、C# 委托 (Delegate)
开发语言·c#·.net
军训猫猫头19 小时前
95.WPF中图片控件的使用与资源路径设置 WPF例子 C#例子
ui·c#·.net·wpf
qq_3404740219 小时前
5 WPF中的Page页面的使用
开发语言·c#·wpf
码观天工20 小时前
.NET AI 生态关键拼图:全面解读 AI Extensions 和 Vector Extensions 如何重塑.NET开发生态
ai·c#·.net·向量数据库