垃圾回收机制与弱引用在.NET中的应用

垃圾回收(Garbage Collection, GC)是现代编程语言中用于自动管理内存的一种机制。它通过识别程序中不再使用的对象,并释放它们占用的内存,从而避免内存泄漏。在.NET环境中,垃圾回收器(GC)负责管理内存,确保应用程序的稳定运行。本文将详细探讨.NET中的垃圾回收算法,以及弱引用(WeakReference)在内存管理中的应用。

垃圾回收算法

.NET中,每个对象都通过托管堆(Managed Heap)进行分配。托管堆之所以被称为“托管”,是因为在.NET环境中分配的每个对象都在垃圾回收器的显式监控之下。当启动一个应用程序时,它会创建自己的地址空间,应用程序使用的内存将存储在这里。运行时维护一个指针,指向堆的基础对象。随着对象的创建,运行时首先检查是否可以在保留空间内创建对象,如果可以,它将创建对象并返回指向该位置的指针,以便应用程序可以维护对该对象的强引用

当垃圾回收器启动时,它假设所有对象都是垃圾,并首先找到所有全局应用于应用程序的强引用,这些被称为应用程序根(Application Roots)。然后,它逐个对象地进行,创建一个从应用程序根开始的所有对象的图,确保图中的每个对象都是唯一的。当这个过程完成后,图将包含所有以某种方式可达的应用程序对象。现在,由于GC已经识别出对应用程序来说不是垃圾的对象,它继续进行压缩。它线性遍历所有对象,并将可达对象移动到不可达空间,这被称为堆压缩(Heap Compaction)。在堆压缩期间移动指针时,所有指针将重新评估,以确保应用程序根指向相同的引用。

弱引用作为例外

在每个GC周期中,大量对象被收集以释放应用程序的内存压力。如前所述,它找到所有以某种方式可达的应用程序根的对象。在垃圾回收期间未被收集的引用被称为强引用,因为根据强引用的定义,可达GC的对象被称为强引用对象。

这会带来一个问题。GC是不确定的。它随机开始释放内存。假设需要一次处理一千个字节的数据,并且在它移除对象的引用后,必须依赖GC再次触发并移除引用的时间。可以使用GC.Collect请求GC开始收集,但这也只是一个请求。

现在假设需要再次使用大对象,并且已经移除了对象的所有引用,并且需要重新创建对象。这就带来了巨大的内存压力。在这种情况下,有:

  • 已经移除了对象的所有引用
  • 垃圾回收没有触发并移除分配的地址
  • 需要再次使用对象

在这种情况下,即使对象仍然在应用程序内存区域中,仍然需要创建另一个对象。这就是弱引用的用武之地。

弱引用的类型

弱引用有两种类型:

  • 短期弱引用:当GC被收集时,短期弱引用会失去引用。在案例中,使用了短期弱引用。
  • 长期弱引用:即使对象的Finalize方法被调用,长期弱引用也会保留。在这种情况下,对象的状态无法确定。在WeakReference的构造函数中传递trackResurrectiontrue,默认为false,以跟踪即使调用了Finalize方法的对象。

WeakReference对象接受一个对象的强引用作为参数,可以使用Target属性检索它。让看一个例子:

代码演示

为了演示这个特性,让创建一个使用大量内存的类。

public class SomeBigClass : List<string> { public SomeBigClass() { this.LoadBigObject(); } private void LoadBigObject() { for (int i = 0; i < 100000; i++) { this.Add(string.Format("String No. {0}", i)); } } }

显然,SomeBigClass是一个包含100000个字符串的列表。代码看起来非常直接,只是创建了一个定义List<string>的替代品。现在让创建另一个类来展示WeakReference类的实际实现。

public class WeakReferenceUsage { WeakReference weakref = null; private SomeBigClass _somebigobject = null; public SomeBigClass SomeBigObject { get { SomeBigClass sbo = null; if (weakref == null) { sbo = new SomeBigClass(); this.weakref = new WeakReference(sbo); this.OnCallBack("Object created for first time"); } else if (weakref.Target == null) { sbo = new SomeBigClass(); weakref.Target = sbo; this.OnCallBack("Object is collected by GC, so new object is created"); } else { sbo = weakref.Target as SomeBigClass; this.OnCallBack("Object is not yet collected, so reusing the old object"); } this._somebigobject = sbo; return this._somebigobject; } set { this._somebigobject = null; } } public event Action<string> CallBack; public void OnCallBack(string info) { if (this.CallBack != null) { this.CallBack(info); } } }

在上面的类中,定义了一个WeakReference的引用作为weakref,它持有SomeBigClass的对象。现在SomeBigClass属性在其中定义了一点逻辑。它使用现有的WeakReference.Target来获取现有对象。如果Target是null,对象将再次被创建并存储在WeakReference Target中。

WeakReference作为现有GC算法的例外。即使对象可以从应用程序中访问,它仍然留给GC收集。所以如果GC触发,它将收集SomeBigClass的对象,WeakReference.Target将失去引用。

为了演示这个类,让创建一个控制台应用程序并创建WeakReferenceUsage的对象。示例类如下:

static void Main(string[] args) { WeakReferenceUsage wru = new WeakReferenceUsage(); wru.CallBack += new Action<string>(wru_CallBack); while (true) { foreach (string fetchstring in wru.SomeBigObject) { Console.WriteLine(fetchstring); } wru.SomeBigObject = null; GC.Collect(); ConsoleKeyInfo info = Console.ReadKey(); if (info.Key == ConsoleKey.Escape) { break; } } } static void wru_CallBack(string obj) { Console.WriteLine(obj); Console.Read(); }

在Main方法中,创建了一个WeakReferenceUsage的对象,并注册了回调,以便每当尝试检索对象时,消息将显示在控制台上。

通过设置:

wru.SomeBigObject = null; GC.Collect();

将销毁强应用程序引用,因此对象将暴露给垃圾收集。调用GC.Collect将请求垃圾收集进行收集。

在第一次运行时,将看到:“对象首次创建”。在获取所有数据后,可能会收到第二条消息,说对象尚未收集,对象是从现有的WeakReference中获取的,或者如果等待很长时间,可能会收到第三条消息,说对象已被GC收集,对象被重新创建。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485