对象复活的用途

15

我的.NET Windows服务应用程序存在内存泄漏的问题。因此,我开始阅读有关.NET中的内存管理的文章。我在Jeffrey Richter的一篇文章中发现了一个有趣的做法。这个做法叫做“对象复活”。它看起来像是把初始化全局或静态变量的代码放在“this”中:

protected override void Finalize() {
     Application.ObjHolder = this;
     GC.ReRegisterForFinalize(this);
}

我知道这是一种不好的做法,但我想了解使用这种做法的模式。如果你知道任何模式,请在这里写下来。


3
如果一个为我工作的开发者编写了那段代码,我会让他们将其删除。 - John Saunders
@John:我完全同意——在我看来,这只是为极端边缘情况而设计的。 - Reed Copsey
3
@John:我清楚地理解了它,我的兴趣只是科学方面的 :) - Vokinneberg
8
这是 汤姆·里德尔 说的话。 - Chris Shouts
4
请注意,在“真正的”C#中,对于这种特定的重写,不允许使用protected override void Finalize() { ... }的表示法。必须使用~NameOfClass() { ... }来定义终结器。 - Jeppe Stig Nielsen
5个回答

7
从同一篇文章中得知:"很少有好的复活用途,如果可能的话,你真的应该避免使用它。"
我能想到的最好的用途是“回收”模式。考虑一个工厂,它生产昂贵的、几乎不可变的对象;例如,通过解析数据文件或反射程序集实例化的对象,或者深度复制“主”对象图形。这个昂贵的过程每次执行时,结果都不太可能改变。因此,你最好避免从头开始实例化;但由于某些设计原因,系统必须能够创建许多实例(不能使用单例),而你的消费者无法知道工厂,以便他们自己“返回”对象;他们可能会注入对象,或者从中获取引用的工厂方法委托。当依赖类超出范围时,通常实例也会超出范围。
一种可能的解决方案是覆盖Finalize(),清理实例的任何可变状态部分,然后只要工厂在作用域内,就将实例重新附加到工厂的某个成员上。这样,垃圾收集过程就可以在这些对象本来应该超出范围并被完全销毁时,“回收”它们的有价值部分。工厂可以查看并查看其“垃圾箱”中是否有任何可回收的对象,如果有,则可以将其打磨并分发出去。只有当进程使用的总对象数量增加时,工厂才必须实例化对象的新副本。
其他可能的用途可能包括一些高度专业的记录器或审计实现,其中你希望在它们死亡后处理这些对象将它们附加到由此进程管理的工作队列上。处理完它们后,它们可以完全销毁。
通常,如果你想让依赖方认为他们正在摆脱一个对象,或者不想麻烦他们,但是你想保留实例,复活可能是一个好工具,但你必须非常仔细地监视它,以避免接收复活引用的对象变成“堆积老鼠”,并将每个已经创建的实例保留在内存中,直到进程的生命周期结束。

1
对象回收可以(而且可能应该)在没有对象复活的情况下完成。我认为更大的使用案例是当一个对象有一个清理操作,只有在整个宇宙中可能不存在对它的任何其他引用时才应该执行。要清除的对象可以持有对“清理器”对象的引用,后者可以创建对需要清理的对象的静态长弱引用。清理器对象的终结器可以检查长弱引用是否已经失效;如果是,它应该销毁对它的静态引用并执行清理操作。 - supercat
如果弱引用没有死亡,清理器对象应该重新注册以进行终结(至少是暂时地“复活”自己)。请注意,如果对“WeakReference”的唯一引用在清理器的某个字段中,则即使其目标仍然存活,“WeakReference”也可能会死亡;清理器必须创建一个静态根引用到“WeakReference”,以确保它只会在其目标死亡时才会死亡。 - supercat

5

推测:在池化的情况下,例如连接池。

您可能会使用它来回收未正确处理但应用程序代码不再持有引用的对象。您无法将它们保留在池中的列表中,因为那会阻止GC回收。


是的,第一个想法是使用具有重新创建次数内部计数器的池化对象,例如。当计数器降至0时,最后一次终结必须被抑制,对象正在死亡。但无论如何,我认为这种池化的实现方式都不好。 - Vokinneberg
@Vokin,计数器策略并不是管理生命周期的唯一方法。我认为主要的重点是从GC中回收资源。 - H H

5
我的一位兄弟曾经在一个高性能模拟平台上工作。他跟我说,在应用程序中,对象构建是应用程序性能的瓶颈之一。似乎这些对象很大,并且需要一些重要的处理才能初始化。
他们实现了一个对象存储库来包含“退役”的对象实例。在构造新对象之前,他们首先会检查存储库中是否已经存在一个对象。
这样做的权衡是增加内存消耗(因为可能存在许多未使用的对象),以换取性能的提高(因为减少了总的对象构造数量)。
请注意,决定实施此模式是基于他们在特定情况下通过分析观察到的瓶颈。我认为这只是个例外情况。

3
我能想到唯一可能使用这个方法的情况是当您试图清理资源时,但资源清理失败。如果有必要重试清理过程,您可以在技术上“重新注册”要完成的对象,希望第二次会成功。尽管如此,在实践中我建议完全避免使用这种方法。

1
据我所知,.net在没有特定顺序的情况下调用终结器。如果您的类包含对其他对象的引用,则在调用终结器时,这些对象可能已经被终结(因此已被Dispose)。如果您决定复活您的对象,则会有对已终结/已处理对象的引用。
class A {
  static Set<A> resurectedA = new Set<A>();
  B b = new B();
  ~A() {
    //will not die. keep a reference in resurectedA.
    resurectedA.Add(this);
    GC.ReRegisterForFinalize(this); 

    //at this point you may have a problem. By resurrecting this you are resurrecting b and b's Finalize may have already been called.
  } 
}
class B : IDisposable {
  //regular IDisposable/Destructor pattern http://msdn.microsoft.com/en-us/library/b1yfkh5e(v=vs.110).aspx
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接