对象复活的用法

Posted

技术标签:

【中文标题】对象复活的用法【英文标题】:Usages of object resurrection 【发布时间】:2011-04-10 11:10:41 【问题描述】:

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

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

我知道这是一种不好的做法,但是我想知道使用这种做法的模式。如果你知道的话,请写在这里。

【问题讨论】:

如果为我工作的开发人员编写了该代码,我会让他们撕掉它。 @John:我完全同意——IMO 确实有一些极端情况。 @John:我很清楚,我的兴趣只是科学:) Tom Riddle 是这么说的。 请注意,在“真实”C# 中,此特定覆盖不允许使用符号 protected override void Finalize() ... 。必须使用~NameOfClass() ... 作为终结器。 【参考方案1】:

我能想到的唯一可能使用它的地方是当您尝试清理资源并且资源清理失败时。如果重试清理过程很关键,从技术上讲,您可以“重新注册”要完成的对象,希望第二次成功。

话虽如此,我会在实践中完全避免这种情况。

【讨论】:

【参考方案2】:

来自同一篇文章:“复活很少有好的用途,如果可能的话,你真的应该避免它。”

我能想到的最佳用途是“回收”模式。考虑一个生产昂贵的、实际上不可变的对象的工厂;例如,通过解析数据文件、反映程序集或深度复制“主”对象图来实例化对象。每次执行这个昂贵的过程时,结果都不太可能发生变化。避免从头开始实例化符合您的最大利益;但是,由于某些设计原因,系统必须能够创建许多实例(没有单例),并且您的消费者无法知道工厂,以便他们可以自己“返回”对象;他们可能已经注入了对象,或者被赋予了工厂方法委托,他们从中获得了引用。当依赖类超出范围时,通常实例也会超出范围。

一个可能的答案是重写 Finalize(),清除实例的任何可变状态部分,然后只要工厂在范围内,就将实例重新附加到工厂的某个成员。这允许垃圾收集过程实际上“回收”这些对象的有价值部分,否则它们将超出范围并被完全销毁。工厂可以查看它的“垃圾箱”中是否有任何可回收的物品,如果有,它可以将其打磨并分发出去。如果进程使用的对象总数增加,工厂只需实例化对象的新副本。

其他可能的用途可能包括一些高度专业化的记录器或审计实现,您希望在其死后处理的对象将自己附加到由该进程管理的工作队列中。在进程处理它们之后,它们可以被完全销毁。

一般来说,如果你想让家属认为他们正在摆脱一个对象,或者不想打扰,但你想保留实例,复活可能是一个很好的工具,但你必须注意它非常小心地避免接收复活引用的对象成为“打包老鼠”的情况,并在进程的生命周期内将每个已创建的实例保存在内存中。

【讨论】:

对象回收可以(并且可能应该)在没有复活的情况下完成。我认为一个更大的用例是当一个对象有一个清理操作时,只有在宇宙中的任何地方都不可能存在对它的其他引用时才应该执行该操作(即使在具有终结器的对象中)。要清理的对象可以持有对“更清洁”对象的引用,该对象可以创建对需要清理的对象的静态长弱引用。 Cleaner 对象的终结器可以检查长弱引用是否已经死亡;如果是这样,它应该销毁对它的静态引用并执行清理; 如果弱引用没有死掉,cleaner 对象应该重新注册完成(有效地复活自己,至少是暂时的)。请注意,如果对WeakReference 的唯一引用是在清洁工的某个字段中,则WeakReference 可能会在其目标还活着的时候死去;清理器必须创建一个对 WeakReference 的静态根引用,以确保它只会在目标死亡时死亡。【参考方案3】:

推测性:在池情况下,例如 ConnectionPool。

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

【讨论】:

是的,例如,第一个想法是池化对象,其中包含要复活的时间。当计数器下降到 0 时,最后一个终结必须被抑制并且对象正在死亡。但无论如何,我认为这不是一个好的池化实现。 @Vokin,反击策略并不是在这里管理生命周期的唯一方法。我认为重点是从 GC 中回收资源。【参考方案4】:

我的一个兄弟曾经在高性能仿真平台上工作过。他告诉我,在应用程序中,对象构造是应用程序性能的明显瓶颈。看起来对象很大,需要一些重要的处理来初始化。

他们实现了一个对象存储库来包含“退休”的对象实例。在构造一个新对象之前,他们首先会检查存储库中是否已经存在一个对象。

权衡是增加内存消耗(因为一次可能存在许多未使用的对象)以提高性能(因为减少了对象构造的总数)。

请注意,实施此模式的决定是基于他们在特定场景中通过分析观察到的瓶颈。我认为这是一种特殊情况。

【讨论】:

【参考方案5】:

据我所知,.net 没有特定顺序调用终结器。如果您的类包含对其他对象的引用,则在调用终结器时它们可能已被终结(并因此被处置)。如果您随后决定复活您的对象,您将获得对最终/已处置对象的引用。

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

【讨论】:

以上是关于对象复活的用法的主要内容,如果未能解决你的问题,请参考以下文章

垃圾回收的可触及性

对象引用分析

Unity 2D复活点的制作

JVM垃圾回收时的可触及性

在线复活节彩蛋搜寻中揭示了Microsoft Edge浏览器的新图标

IOS应用崩溃复活