垃圾回收(Garbage Collection, GC)是现代编程语言中用于自动管理内存的一种机制。它通过识别程序中不再使用的对象,并释放它们占用的内存,从而避免内存泄漏。在.NET环境中,垃圾回收器(GC)负责管理内存,确保应用程序的稳定运行。本文将详细探讨.NET中的垃圾回收算法,以及弱引用(WeakReference)在内存管理中的应用。
在.NET中,每个对象都通过托管堆(Managed Heap)进行分配。托管堆之所以被称为“托管”,是因为在.NET环境中分配的每个对象都在垃圾回收器的显式监控之下。当启动一个应用程序时,它会创建自己的地址空间,应用程序使用的内存将存储在这里。运行时维护一个指针,指向堆的基础对象。随着对象的创建,运行时首先检查是否可以在保留空间内创建对象,如果可以,它将创建对象并返回指向该位置的指针,以便应用程序可以维护对该对象的强引用。
当垃圾回收器启动时,它假设所有对象都是垃圾,并首先找到所有全局应用于应用程序的强引用,这些被称为应用程序根(Application Roots)。然后,它逐个对象地进行,创建一个从应用程序根开始的所有对象的图,确保图中的每个对象都是唯一的。当这个过程完成后,图将包含所有以某种方式可达的应用程序对象。现在,由于GC已经识别出对应用程序来说不是垃圾的对象,它继续进行压缩。它线性遍历所有对象,并将可达对象移动到不可达空间,这被称为堆压缩(Heap Compaction)。在堆压缩期间移动指针时,所有指针将重新评估,以确保应用程序根指向相同的引用。
在每个GC周期中,大量对象被收集以释放应用程序的内存压力。如前所述,它找到所有以某种方式可达的应用程序根的对象。在垃圾回收期间未被收集的引用被称为强引用,因为根据强引用的定义,可达GC的对象被称为强引用对象。
这会带来一个问题。GC是不确定的。它随机开始释放内存。假设需要一次处理一千个字节的数据,并且在它移除对象的引用后,必须依赖GC再次触发并移除引用的时间。可以使用GC.Collect
请求GC开始收集,但这也只是一个请求。
现在假设需要再次使用大对象,并且已经移除了对象的所有引用,并且需要重新创建对象。这就带来了巨大的内存压力。在这种情况下,有:
在这种情况下,即使对象仍然在应用程序内存区域中,仍然需要创建另一个对象。这就是弱引用的用武之地。
弱引用有两种类型:
trackResurrection
为true
,默认为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收集,对象被重新创建。