.NET 垃圾收集器最终会释放内存,但如果您希望立即恢复该内存怎么办?您需要在类中使用什么代码 MyClass 打电话

MyClass.Dispose()

并释放变量和对象所使用的所有空间 MyClass?

有帮助吗?

解决方案

IDisposable 与释放内存无关。IDisposable 是一种释放模式 不受管理的 资源——而内存绝对是一种托管资源。

指向 GC.Collect() 的链接是正确的答案,尽管 Microsoft .NET 文档通常不鼓励使用此函数。

编辑: 由于这个答案已经赢得了大量的因果报应,我觉得有责任详细说明它,以免 .NET 资源管理的新手得到错误的印象。

在 .NET 进程内部,有两种资源——托管资源和非托管资源。“托管”意味着运行时控​​制资源,而“非托管”意味着这是程序员的责任。如今,我们在 .NET 中实际上只关心一种托管资源——内存。程序员告诉运行时分配内存,然后由运行时决定何时可以释放内存。.NET 为此目的使用的机制称为 垃圾收集 只需使用 Google,您就可以在互联网上找到大量有关 GC 的信息。

对于其他类型的资源,.NET 不知道如何清理它们,因此它必须依赖程序员来做正确的事情。为此,平台为程序员提供了三种工具:

  1. VB 和 C# 中的 IDisposable 接口和“using”语句
  2. 终结器
  3. 许多 BCL 类实现的 IDisposable 模式

第一个允许程序员有效地获取资源,使用它,然后在同一个方法中释放它。

using (DisposableObject tmp = DisposableObject.AcquireResource()) {
    // Do something with tmp
}
// At this point, tmp.Dispose() will automatically have been called
// BUT, tmp may still a perfectly valid object that still takes up memory

如果“AcquireResource”是一个工厂方法(例如)打开一个文件并且“Dispose”自动关闭该文件,则此代码不会泄漏文件资源。但是“tmp”对象本身的内存很可能仍然被分配。这是因为 IDisposable 接口与垃圾收集器完全没有联系。如果你 做过 想要确保内存被释放,你唯一的选择是调用 GC.Collect() 强制垃圾收集。

然而,无论如何强调这可能不是一个好主意。通常,让垃圾收集器执行其设计目的(即管理内存)要好得多。

如果资源使用时间较长,导致其生命周期跨越多种方法,会发生什么情况?显然,“using”语句不再适用,因此程序员在使用完资源后必须手动调用“Dispose”。如果程序员忘记了怎么办?如果没有后备措施,则进程或计算机最终可能会耗尽未正确释放的资源。

这就是终结器发挥作用的地方。终结器是类中与垃圾收集器有特殊关系的方法。GC 承诺,在为该类型的任何对象释放内存之前,它首先会给终结器一个进行某种清理的机会。

所以对于文件来说,理论上我们根本不需要手动关闭文件。我们可以等待垃圾收集器到达它,然后让终结器完成工作。不幸的是,这在实践中效果不佳,因为垃圾收集器的运行是不确定的。文件保持打开状态的时间可能比程序员预期的要长得多。如果有足够的文件保持打开状态,则系统在尝试打开其他文件时可能会失败。

对于大多数资源,我们都需要这两件事。我们希望约定能够说“我们现在已经完成了该资源”,并且我们希望确保如果我们忘记手动进行清理,至少有机会自动进行清理。这就是“IDisposable”模式发挥作用的地方。这是一个允许 IDispose 和终结器很好地协同工作的约定。您可以通过查看该模式来了解该模式的工作原理 IDisposable 的官方文档.

底线: 如果您真正想做的只是确保释放内存,那么 IDisposable 和终结器将无法帮助您。但 IDisposable 接口是所有 .NET 程序员都应该理解的极其重要模式的一部分。

其他提示

您只能处置实现 IDisposable 接口的实例。

要强制垃圾收集立即释放(非托管)内存:

GC.Collect();  
GC.WaitForPendingFinalizers();

这通常是不好的做法,但是例如 .NET 框架的 x64 版本中存在一个错误,该错误使 GC 在某些情况下表现得很奇怪,然后您可能想要这样做。不知道bug是否已经解决了。有人知道吗?

要处置一个类,您可以这样做:

instance.Dispose();

或者像这样:

using(MyClass instance = new MyClass())
{
    // Your cool code.
}

这将在编译时翻译为:

MyClass instance = null;    

try
{
    instance = new MyClass();        
    // Your cool code.
}
finally
{
    if(instance != null)
        instance.Dispose();
}

您可以像这样实现 IDisposable 接口:

public class MyClass : IDisposable
{
    private bool disposed;

    /// <summary>
    /// Construction
    /// </summary>
    public MyClass()
    {
    }

    /// <summary>
    /// Destructor
    /// </summary>
    ~MyClass()
    {
        this.Dispose(false);
    }

    /// <summary>
    /// The dispose method that implements IDisposable.
    /// </summary>
    public void Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }

    /// <summary>
    /// The virtual dispose method that allows
    /// classes inherithed from this one to dispose their resources.
    /// </summary>
    /// <param name="disposing"></param>
    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Dispose managed resources here.
            }

            // Dispose unmanaged resources here.
        }

        disposed = true;
    }
}

对这个问题的回答已经变得有点混乱了。

标题询问了如何处置,但随后又说他们希望立即恢复记忆。

.Net 是 管理, ,这意味着当您编写.Net应用程序时,您不需要直接担心内存,代价是您也无法直接控制内存。

.Net 决定何时最好清理和释放内存,而不是由 .Net 编码员决定。

Dispose 是一种告诉 .Net 您已经完成某件事的方法,但在最佳时机之前它实际上不会释放内存。

基本上,.Net 实际上会在最容易的时候回收内存——它非常擅长决定何时回收。除非您正在编写非常占用内存的内容,否则通常不需要否决它(这是游戏通常不使用 .Net 编写的部分原因 - 它们需要完全控制)

在.Net中你可以使用 GC.Collect() 立即强制执行,但这几乎总是不好的做法。如果 .Net 还没有清理它,那就意味着现在还不是清理它的好时机。

GC.Collect() 拾取 .Net 识别为已完成的对象。如果您尚未处置需要它的对象,.Net 可能会决定保留该对象。这意味着 GC.Collect() 仅当您正确实施一次性实例时才有效。

GC.Collect()不是 正确使用 IDisposable 的替代品。

所以Dispose和内存没有直接关系,但也没有必要。正确的处理将使您的 .Net 应用程序更加高效,从而使用更少的内存。


在 .Net 中 99% 的情况下,以下是最佳实践:

规则1: 如果你什么都不处理 不受管理的 或者实现 IDisposable 那么不用担心 Dispose。

规则 2: 如果您有一个实现 IDisposable 的局部变量,请确保在当前作用域中删除它:

//using is best practice
using( SqlConnection con = new SqlConnection("my con str" ) )
{
    //do stuff
} 

//this is what 'using' actually compiles to:
SqlConnection con = new SqlConnection("my con str" ) ;
try
{
    //do stuff
}
finally
{
    con.Dispose();
}

规则 3: 如果一个类具有实现 IDisposable 的属性或成员变量,那么该类也应该实现 IDisposable。在该类的 Dispose 方法中,您还可以处置 IDisposable 属性:

//rather basic example
public sealed MyClass :
   IDisposable
{   
    //this connection is disposable
    public SqlConnection MyConnection { get; set; }

    //make sure this gets rid of it too
    public Dispose() 
    {
        //if we still have a connection dispose it
        if( MyConnection != null )
            MyConnection.Dispose();

        //note that the connection might have already been disposed
        //always write disposals so that they can be called again
    }
}

这并不完整,这就是该示例被密封的原因。继承类可能需要遵守下一条规则......

规则 4: 如果一个类使用 不受管理的 资源然后实现 IDispose 添加终结器。

.Net 不能做任何事情 不受管理的 资源,所以现在我们谈论内存。如果不清理它,可能会出现内存泄漏。

Dispose 方法需要处理这两个问题 管理不受管理的 资源。

终结器是一个安全措施 - 它确保如果其他人创建了您的类的实例并且未能将其处理为“危险” 不受管理的 .Net 仍然可以清理资源。

~MyClass()
{
    //calls a protected method 
    //the false tells this method
    //not to bother with managed
    //resources
    this.Dispose(false);
}

public void Dispose()
{
    //calls the same method
    //passed true to tell it to
    //clean up managed and unmanaged 
    this.Dispose(true);

    //as dispose has been correctly
    //called we don't need the 

    //'backup' finaliser
    GC.SuppressFinalize(this);
}

最后,这个带有布尔标志的 Dispose 重载:

protected virtual void Dispose(bool disposing)
{
    //check this hasn't been called already
    //remember that Dispose can be called again
    if (!disposed)
    {
        //this is passed true in the regular Dispose
        if (disposing)
        {
            // Dispose managed resources here.
        }

        //both regular Dispose and the finaliser
        //will hit this code
        // Dispose unmanaged resources here.
    }

    disposed = true;
}

请注意,一旦一切就绪,创建类实例的其他托管代码就可以像任何其他 IDisposable 一样对待它(规则 2 和 3)。

还应该提到 dispose 并不总是指内存吗?我比内存更频繁地处理资源(例如对文件的引用)。GC.Collect() 直接与 CLR 垃圾收集器相关,并且可能会也可能不会释放内存(在任务管理器中)。它可能会对您的应用程序产生负面影响(例如性能)。

归根结底,你为什么要立即恢复记忆?如果其他地方存在内存压力,大多数情况下操作系统都会为您获取内存。

看看这个 文章

实现 Dispose 模式、IDisposable 和/或终结器与内存何时回收完全无关;相反,它与告诉 GC 有关 如何 来找回那段记忆。当您调用 Dispose() 时,您绝不会与 GC 进行交互。

GC 仅在确定需要时才会运行(称为内存压力),然后(并且只有那时)它才会为未使用的对象释放内存并压缩内存空间。

可以 调用 GC.Collect() 但你真的不应该除非有 非常 有充分的理由(几乎总是“从不”)。当您强制执行这样的带外收集周期时,实际上会导致 GC 执行更多工作,并最终会损害应用程序的性能。在 GC 收集周期的持续时间内,您的应用程序实际上处于冻结状态……运行的 GC 周期越多,您的应用程序冻结的时间就越多。

您还可以进行一些本机 Win32 API 调用来释放工作集,但即使是这些也应该避免,除非有 非常 这样做的充分理由。

垃圾收集运行时背后的整个前提是,您不需要(过多)担心运行时何时分配/释放实际内存;您只需要担心确保您的对象知道如何在被询问时自行清理。

public class MyClass : IDisposable
{
    public void Dispose()
    {
       // cleanup here
    }
}

那么你可以做这样的事情

MyClass todispose = new MyClass();
todispose.Dispose(); // instance is disposed right here

或者

using (MyClass instance = new MyClass())
{

}
// instance will be disposed right here as it goes out of scope

乔·达菲 (Joe Duffy) 对“处置、完成和资源管理":

在.NET Framework的一生中,C#程序员始终将最终变化器称为毁灭者。随着时间的流逝,随着我们变得更聪明,我们正试图接受以下事实 处置方法确实等同于C ++破坏者(确定性), ,而 最终制度是完全分开的(非确定性). 。c#借用C ++破坏者语法的事实(即〜t())肯定至少与这种错误称呼的发展有关。

我写了关于析构函数、处置和垃圾收集的摘要 http://codingcraftsman.wordpress.com/2012/04/25/to-dispose-or-not-to-dispose/

回答原来的问题:

  1. 不要试图管理你的记忆
  2. Dispose 不是内存管理,而是非托管资源管理
  3. 终结器是 Dispose 模式的固有部分,实际上会减慢托管对象的内存释放速度(因为它们必须进入 Finalization 队列,除非已经 Dispose)
  4. GC.Collect 很糟糕,因为它使一些短命对象看起来需要更长时间,从而减慢了它们的收集速度。

但是,如果您有代码的性能关键部分并且希望减少垃圾收集减慢速度的可能性,则 GC.Collect 可能会很有用。你之前这么称呼。

最重要的是,有一个支持这种模式的论点:

var myBigObject = new MyBigObject(1);
// something happens
myBigObject = new MyBigObject(2);
// at the above line, there are temporarily two big objects in memory and neither can be collected

myBigObject = null; // so it could now be collected
myBigObject = new MyBigObject(2);

但主要的答案是垃圾收集只会起作用,除非你乱搞它!

你不能真正强制 GC 在你想要的时候清理一个对象,尽管有很多方法可以强制它运行,但没有什么说它会清理你想要/期望的所有对象。最好以 try catch ex finally dispose end try (VB.NET rulz) 的方式调用 dispose。但 Dispose 用于清理系统资源(内存、句柄、数据库连接等)。由对象以确定性方式分配。Dispose 不会(也不能)清理对象本身使用的内存,只有 GC 才能做到这一点。

本文 有一个非常简单的演练。然而, 拥有 调用 GC 而不是让它自然而然地进行通常是设计/内存管理不良的标志, 尤其 如果没有消耗有限的资源(连接、句柄、通常导致实现 IDisposable 的任何其他内容)。

是什么原因导致您需要这样做?

抱歉,这里选择的答案不正确。正如一些人随后所说的那样,Dispose 和实现 IDisposable 与释放与 .NET 类关联的内存无关。它主要和传统上用于释放非托管资源,例如文件句柄等。

虽然您的应用程序可以调用 GC.Collect() 来尝试强制垃圾收集器进行收集,但这只会对易碎队列中处于正确生成级别的那些项目产生真正的影响。因此,如果您已清除对该对象的所有引用,则在释放实际内存之前,可能仍需要调用几次 GC.Collect() 。

您没有在问题中说出为什么您觉得需要立即释放内存。我知道有时可能会出现异常情况,但认真地说,在托管代码中,几乎总是最好让运行时处理内存管理。

如果您认为您的代码使用内存的速度比 GC 释放内存的速度快,那么您应该检查您的代码,以确保静态成员等中的任何数据结构中不再引用不再需要的对象,这可能是最好的建议。还要尽量避免出现循环对象引用的情况,因为这些引用也可能无法释放。

@基思,

我同意你的所有规则,除了第 4 条规则。仅应在非常特殊的情况下添加终结器。如果类使用非托管资源,则应在 Dispose(bool) 函数中清除这些资源。这个相同的函数应该只在 bool 为 true 时清理托管资源。添加终结器会增加使用对象的复杂性成本,因为每次创建新实例时,它还必须放置在终结队列中,每次 GC 运行收集周期时都会检查该队列。实际上,这意味着您的对象的生存周期/生成时间比应有的时间长,因此终结器可以运行。终结器不应被视为“安全网”。

GC 仅在确定 Gen0 堆中没有足够的可用内存来执行下一次分配时才会运行收集周期,除非您通过调用 GC.Collect() 来强制进行带外收集来“帮助”它。

底线是,无论如何,GC 只知道如何通过调用 Dispose 方法(如果实现了的话,还可能调用终结器)来释放资源。该方法负责“做正确的事情”并清理使用的任何非托管资源并指示任何其他托管资源调用其 Dispose 方法。它的工作效率非常高,并且只要不受带外收集周期的帮助,就可以在很大程度上进行自我优化。话虽如此,如果不显式调用 GC.Collect,您将无法控制何时以及以何种顺序处理对象和释放内存。

如果 MyClass 实现了 IDisposable,你就可以做到这一点。

MyClass.Dispose();

C# 的最佳实践是:

using( MyClass x = new MyClass() ) {
    //do stuff
}

这样就完成了 try-finally 的处理,并确保它永远不会被错过。

如果你不想(或不能)在你的类上实现 IDisposable,你可以像这样强制垃圾收集(但它很慢) -

GC.Collect();

IDisposable 接口实际上适用于包含非托管资源的类。如果你的类不包含非托管资源,为什么你要 需要 在垃圾收集器之前释放资源吗?否则,只需确保您的对象尽可能晚地实例化并尽快超出范围即可。

您可以在 C++ 中进行确定性对象销毁

您永远不想调用 GC.Collect,它会扰乱垃圾收集器的自我调整以检测内存压力,并且在某些情况下除了增加堆上每个对象的当前生成之外什么也不做。

对于那些发布 IDisposable 答案的人。调用 Dispose 方法不会像提问者所描述的那样销毁对象。

@基思:

IDisposable 用于托管资源。

终结器适用于非托管资源。

抱歉,但这是错误的。通常,终结器什么也不做。然而,如果 处置模式 已正确实现,终结器尝试调用 Dispose.

Dispose 有两份工作:

  • 免费的非托管资源,以及
  • 免费嵌套托管资源。

在这里,您的声明发挥了作用,因为在最终确定时,对象确实不应该尝试释放嵌套的托管资源,因为这些资源可能已经被释放了。但它仍然必须释放非托管资源。

尽管如此,终结器除了调用之外没有别的工作 Dispose 并告诉它不要触摸托管对象。 Dispose, ,当手动调用时(或通过 Using),应释放所有非托管资源并通过 Dispose 消息到嵌套对象(和基类方法),但这将 绝不 释放任何(托管)内存。

康拉德·鲁道夫 - 是的,通常终结者什么也不做。除非您正在处理非托管资源,否则不应实现它。

然后,当你实现它时,你使用 微软的处置模式 (如前所述)

  • public Dispose() 来电 protected Dispose(true) - 处理托管和非托管资源。呼唤 Dispose() 应该抑制最终确定。

  • ~Finalize 来电 protected Dispose(false) - 仅处理非托管资源。如果您无法调用,这可以防止非托管内存泄漏 public Dispose()

~Finalize 速度很慢,除非您确实有非托管资源需要处理,否则不应使用。

托管资源不会内存泄漏,它们只会浪费当前应用程序的资源并减慢其垃圾收集速度。非托管资源可能会泄漏,并且 ~Finalize 是确保他们不会这样做的最佳实践。

在任一情况下 using 是最佳实践。

@Curt Hagenlocher - 这是从后到前的。我不知道为什么这么多人在错误的情况下投票赞成它。

IDisposable 是为了 管理 资源。

决赛选手是为了 不受管理的 资源。

只要您只使用托管资源,@Jon Limjap 和我自己都是完全正确的。

对于使用非托管资源的类(请记住,绝大多数 .Net 类不使用),Patrik 的答案是全面且最佳实践。

避免使用 GC.Collect - 这是一种处理托管资源的缓慢方法,并且不会对非托管资源执行任何操作,除非您已正确构建 ~Finalizers。


我已根据以下内容从原始问题中删除了主持人评论 https://stackoverflow.com/questions/14593/e​​tiquette-for-modifying-posts

在回答最初的问题时,根据原始发布者迄今为止提供的信息,可以 100% 确定他对 .NET 编程了解不够,甚至无法给出答案:使用 GC.Collect()。我想说,正如大多数发帖者所指出的那样,他有 99.99% 的可能根本不需要使用 GC.Collect() 。

正确的答案归结为“让 GC 完成它的工作”。时期。你还有其他事情要担心。但您可能需要考虑是否以及何时应该处置或清理特定对象,以及是否需要在类中实现 IDisposable 和 Finalize。

关于基思的帖子和他的规则#4:

有些发帖者混淆了规则 3 和规则 4。基思的规则 4 毫无疑问是绝对正确的。这是四个规则中的一个,根本不需要编辑。我会稍微改写他的一些其他规则,以使它们更清晰,但如果您正确解析它们,并且实际上阅读整篇文章以了解他如何扩展它们,它们本质上是正确的。

  1. 如果您的类不使用非托管资源,并且它也从不实例化本身直接或最终使用非托管对象(即实现 IDisposable 的类)的类的另一个对象,那么您的类就不需要要么实现 IDisposable 本身,要么甚至对任何东西调用 .dispose。(在这种情况下,认为您实际上需要立即通过强制 GC 释放内存的想法是愚蠢的。)

  2. 如果您的类使用非托管资源,或者实例化另一个本身实现 IDisposable 的对象,那么您的类应该:

    a)立即在创建它们的本地上下文中处置/释放它们,或者...

    b) 按照 Keith 的帖子中推荐的模式、互联网上的几千个地方、或者现在大约 300 本书中推荐的模式来实现 IDisposable。

    b.1) 此外,如果 (b) 并且它是已打开的非托管资源,则根据 Keith 规则 #4,应该始终实现 IDisposable 和 Finalize。
    在这种情况下,Finalize 在某种意义上绝对是一个安全网:如果有人实例化您的使用非托管资源的 IDisposable 对象,并且他们无法调用 dispose,则 Finalize 是您的对象正确关闭非托管资源的最后机会。
    (Finalize 应该通过调用 Dispose 来完成此操作,以便 Dispose 方法跳过释放除非托管资源之外的任何内容。或者,如果对象的 Dispose 方法被实例化对象的任何对象正确调用,那么它既会将 Dispose 调用传递给它已实例化的所有 IDisposable 对象,又正确释放非托管资源,最后调用抑制对象上的 Finalize ,这意味着如果调用者正确处置了您的对象,则使用 Finalize 的影响会减少。顺便说一句,所有这些要点都包含在 Keith 的帖子中。)

    b.2) 如果您的类仅实现 IDisposable,因为它本质上需要将 Dispose 传递给它已实例化的 IDisposable 对象,那么在这种情况下,请勿在您的类中实现 Finalize 方法。Finalize 用于处理以下情况:Dispose 从未被实例化对象的任何对象调用,并且使用了尚未释放的非托管资源。

简而言之,关于基思的帖子,他是完全正确的,在我看来,该帖子是最正确和完整的答案。他可能会使用一些一些人认为“错误”或反对的简短陈述,但他的完整帖子完全扩展了 Finalize 的用法,他是绝对正确的。在跳入他的帖子中的任何规则或初步声明之前,请务必完整阅读他的帖子。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top