自学内容网 自学内容网

GC的算法

在C# 中,垃圾回收器(GC, Garbage Collector)是一个自动内存管理系统。C# 的 GC 主要采用标记-清除(Mark-Sweep)和分代回收(Generational Collection)的方式来高效管理和回收不再使用的内存。下面分别介绍这两种机制。


1. 标记-清除(Mark-Sweep)

工作原理:

标记-清除是垃圾回收的核心算法之一,主要分为两个阶段:标记阶段和清除阶段。

  1. 标记阶段(Mark phase)

    • 从**根对象(Root objects)**出发(如全局变量、线程栈上的局部变量和静态变量),GC 遍历所有直接或间接引用的对象,将这些对象标记为“存活”。
    • 这个过程类似于图的遍历:GC 通过深度优先或广度优先的方式遍历对象引用图,标记所有可达的对象。
  2. 清除阶段(Sweep phase)

    • 完成标记后,所有未被标记的对象都被认为是垃圾(即不再需要的内存)。
    • GC 会遍历堆中所有对象,并清除未被标记的对象,回收这些对象所占用的内存。

优点:

  • 简单有效:算法简单,能够很好地处理对象的引用和生命周期管理。

缺点:

  • 碎片化问题:由于标记-清除只是简单地释放内存,回收的内存位置可能是不连续的,导致堆内存碎片化,从而影响后续内存分配效率。
  • 暂停时间较长:GC 必须暂停程序执行,等待标记和清除操作完成,可能导致短暂的程序“卡顿”。

2. 分代回收(Generational Collection)

工作原理:

分代回收是基于对象生命周期的观察进行优化的一种垃圾回收策略。C# 的垃圾回收器将堆分为三代,每代对象的回收策略有所不同:

  1. 第0代(Gen 0)

    • 短命对象:新分配的对象会首先进入第0代。这一代通常包含生命周期较短的对象,诸如局部变量等。
    • 快速回收:由于大多数对象很快就会变得不可达,第0代会被频繁回收。GC 在回收第0代时,只检查第0代中的对象是否存活,回收其余内存。
  2. 第1代(Gen 1)

    • 中期存活对象:如果第0代中的对象在一次GC回收后依然存活,它们会被提升到第1代。
    • 不太频繁回收:第1代的回收频率比第0代低,通常用于回收生命周期较长但最终会被释放的对象。
  3. 第2代(Gen 2)

    • 长期存活对象:如果第1代中的对象依然存活,它们会被提升到第2代。第2代主要存放生命周期较长的对象,比如全局对象、缓存等。
    • 较少回收:第2代的回收最为不频繁,因为这些对象可能长期存在。回收第2代通常是内存消耗较大时才触发的(称为完全回收)。

为什么分代回收有效:

  • 对象生命周期的特点:大部分对象的生命周期较短,因此第0代频繁回收可以快速释放大量内存。同时,由于较长生命周期的对象数量较少且不经常变化,减少对第1代和第2代的回收频率可以提升性能。

分代回收的策略:

  • 代内的局部性:GC 可以只在特定代进行回收,减少不必要的工作,避免完全扫描所有对象,从而提高效率。
  • 提升机制:当一个对象在一次回收后依然存活,GC 会认为它可能长期存活,因此将其提升到更高的一代。提升后的对象在下一代会经历较少的回收频率。

优点:

  • 高效的内存管理:分代回收利用了对象生命周期的局部性,能够在不频繁扫描整个堆的情况下,快速回收短生命周期对象,降低GC开销。
  • 减少暂停时间:因为可以针对特定代进行垃圾回收,避免对整个内存进行全面扫描,减少了程序因垃圾回收暂停的时间。

缺点:

  • 提升可能带来开销:对象从第0代到第1代、再到第2代的提升会带来一些额外的开销,尤其是当对象需要频繁提升时。
  • 第2代回收代价高:第2代对象通常是大对象,回收时可能涉及复杂的操作,并且当第2代触发回收时,会导致一次较长的暂停。

3. 标记-压缩 (Mark-Compact)

标记压缩算法是在标记清除算法的基础之上,做了优化改进的算法。
不过在 标记阶段后它将所有活动对象紧密的排在堆的一侧(压缩),消除了内存碎片,然后清理边界以外的垃圾,从而 解决了碎片化的问题
不过压缩是需要花费计算成本的。

缺点

  • 压缩过程的开销,需要多次搜索堆
  • 标记后需要定位各个活动对象的新内存地址,然后再移动对象,总共搜索了3次堆。

4. 引用计数法(Reference Count)

引用技术算法是唯一一种不用用到根集概念的GC算法。其基本思路是为每个对象加一个计数器,计数器记录的是所有指向该对象的引用数量。每次有一个新的引用指向这个对象时,计数器加一;反之,如果指向该对象的引用被置空或指向其它对象,则计数器减一。当计数器的值为0时,则自动删除这个对象。

缺点:

  • 最大的缺点,就是无法释放循环引用的对象。
  • 必须在引用发生增减时对引用计数做出正确的增减,而如果漏掉了某个增减的话,就会引发很难找到原因的内存错误。
  • 引用计数管理并不适合并行处理。

C# 的垃圾回收器通过结合这两种机制,能够在保持良好性能的同时,自动管理内存的分配与回收,极大地简化了开发者的工作。


原文地址:https://blog.csdn.net/ccccco1/article/details/142312710

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!