C# Weak Reference

2023-09-21 14:21:19 浏览数 (2)

1.概要

在C#中,弱引用(Weak Reference)是对一个对象的引用,它不会阻止系统垃圾回收器回收这个对象。当垃圾回收器运行时,如果一个对象只被弱引用指向,那么这个对象可以被回收以释放内存。如果应用程序的代码可以访问一个正由该程序使用的对象,垃圾回收器就不能回收该对象, 那么,就认为应用程序对该对象具有强引用。弱引用允许应用程序访问对象,同时也允许垃圾回收器收集相应的对象。如果不存在强引用,则弱引用的有限期只限于收集对象前的一个不确定的时间段。使用弱引用时,应用程序仍可对该对象进行强引用,这样做可防止该对象被收集。但始终存在这样的风险:垃圾回收器在重新建立强引用之前先处理该对象。

占用大量内存,但通过垃圾回收功能回收以后很容易重新创建的对象特别适合使用弱引用。

弱引用的特点

  1. 垃圾回收:弱引用指向的对象在内存需要清理时可以被垃圾回收器回收,即使该对象仍然被弱引用引用。所以当你试图访问这个对象时,可能会发现它已经不再存在。
  2. 避免内存泄漏:弱引用在处理大对象或者防止内存泄漏等场景下很有用。弱引用允许您在不阻止垃圾回收的情况下保留对对象的引用。
  3. 生命周期管理:弱引用为.NET提供了更灵活的生命周期管理机制,程序可以根据需要创建短周期或长周期的弱引用。
  4. 可选性的跟踪:在创建弱引用时,可以选择是否跟踪对象的终结过程。如果选择跟踪,那么即使对象被垃圾回收,弱引用仍然可以返回一个可用的对象。这可以用于实现对象池或缓存等场景。
  5. 复杂性:弱引用比强引用更难以正确使用,因为需要注意对象是否已被垃圾回收。在使用前需要先检查弱引用是否还有效(即,它所引用的对象是否还存在)。

弱引用的优点和缺点

优点:

  1. 内存管理:弱引用提供了一种方法,使得你能够引用对象而不会阻止垃圾回收器对该对象进行回收。这在处理大型数据结构或缓存时特别有用。
  2. 防止内存泄漏:由于弱引用不会阻止垃圾回收器回收其指向的对象,因此弱引用有助于防止内存泄漏。
  3. 灵活性:弱引用允许更复杂和灵活的对象生命周期管理。你可以使用它们创建短周期或长周期的引用。

缺点:

  1. 复杂性:正确地使用弱引用较为复杂,需要时刻注意对象可能已经被垃圾回收。在使用前需要检查弱引用是否仍然有效(即,它所引用的对象是否还存在)。
  2. 性能开销:创建和使用弱引用需要额外的资源,可能会影响应用程序的性能。
  3. 不可预测的行为:因为弱引用的目标对象何时被垃圾回收是不可预知的,这可能导致不可预测的行为。
  4. 跟踪困难:如果一个对象被意外地回收,弱引用可能会变得无效,这可能会导致一些难以跟踪的错误。

尽管弱引用有其用途,但在大多数情况下,你可能不需要直接使用它们。只有在设计大型数据结构或缓存,或者在其他需要精细控制对象生命周期的场景中,才需要考虑使用弱引用。

使用弱引用的场景

  1. 缓存:如果你正在实现一个缓存,弱引用可以很有用。例如,你可能要缓存一些大对象或者计算成本很高的数据。当系统内存充足时,这些对象会保留在缓存中。但是,当系统内存紧张时,这些对象可以被垃圾回收器回收,以便为其他更重要的对象释放空间。
  2. 大型对象和资源:对于占用大量内存或需要显著计算开销来创建的对象,弱引用也很有用。使用弱引用可以让这些对象在不再必要时被垃圾回收。
  3. 事件监听器:在.NET中,事件订阅者通常是通过强引用从事件发布者那里获取的,这可能导致无法预期的生命周期扩展和潜在的内存泄漏。在这种情况下,使用弱引用可避免生命周期的延长。
  4. 可选关联:有时,您可能希望在两个对象之间建立一个可选的链接,即使其中一个对象被删除,另一个对象也可以继续存在。弱引用可以满足这种需求。
  5. 元数据关联:如果你需要将一些元数据(如附加属性或调试信息)与某个对象关联起来,但又不希望这种关联影响到对象的生命周期,那么可以使用弱引用。

弱引用最好在你确实需要控制内存使用或管理复杂的对象生命周期时才使用。错误的使用弱引用可能会导致难以调试的问题,因为弱引用的目标对象可能在任何时间被自动删除。

使用弱引用关键步骤和注意事项

创建弱引用:在C#中,可以通过WeakReference类来创建一个弱引用。例如:

代码语言:javascript复制
// 创建强引用对象
object obj = new object();

// 创建弱引用
WeakReference weakRef = new WeakReference(obj);

访问弱引用的目标对象:要访问弱引用指向的对象,需要使用Target属性,并且在此之前,最好使用IsAlive属性检查该对象是否还存在:

代码语言:javascript复制
if (weakRef.IsAlive)
{
    obj = weakRef.Target as object;
}

防止早期回收:当你创建了一个弱引用后,应避免保留原始的强引用,否则该对象不会被垃圾回收。

适当时机的使用:只有在需要大量内存并且这些内存可以在任何时间被释放的情况下才使用弱引用。如果一个对象需要长期保持活动状态,或者它占用的内存小,那么就不需要使用弱引用。

空值处理:由于弱引用的对象可能会在任何时间被删除,所以在访问前需进行空值检查,确保代码能够正确处理返回值为 null 的情况。

谨慎使用终结器:默认情况下,如果对象拥有终结器,弱引用将继续引用对象直到运行终结器。您可以创建“长”弱引用(通过在构造WeakReference时传递true)来更改此行为,但必须谨慎操作,以免出现意外的内存泄漏。

弱事件模式:在实现事件监听器时,考虑使用弱事件模式来避免事件源无法因订阅者已经不存在而被垃圾回收带来的内存泄漏。

请记住,尽管弱引用在某些情况下可能非常有用,但在大部分情况下你可能并不需要它们。只有在确实需要精细控制对象生命周期时,才建议使用弱引用。

2.详细内容

这里使用一段不太合适的代码作为简单的演示,为什么这么说大家看看执行。

代码语言:javascript复制
public class TestClass
{
    ~TestClass()
    {
        // 这是一个终结器
    }
}

// 创建一个强引用对象
TestClass strongRef = new TestClass();

// 对强引用对象进行弱引用
WeakReference weakRef = new WeakReference(strongRef);

// 释放强引用
strongRef = null;

// 调用垃圾回收器
GC.Collect();
GC.WaitForPendingFinalizers(); // 等待终结器完成

// 检查弱引用的状态
Console.WriteLine(weakRef.IsAlive ? "Object has not been collected." : "Object has been collected."); 

执行完代码之后会发现结果一直都是"Object has not been collected." 为什么?

可能是因为垃圾回收器并不总是立即运行,它的运行取决于许多因素,包括系统的内存压力、CLR的实现细节等。即使调用了GC.Collect()方法,也不能100%保证垃圾回收器会立即回收所有的待处理对象。

另外,如果被弱引用的对象实现了终结器(Finalizer)或者析构函数,那么对象的清理过程会被延迟,因为CLR需要执行对象的终结器,然后在下一次垃圾回收时才真正清理该对象。这种情况下,即使强引用已经被置为null,并且调用了GC.Collect()weakRef.IsAlive仍然可能返回true。

Ref

https://learn.microsoft.com/zh-cn/dotnet/standard/garbage-collection/weak-references

0 人点赞