首页 > C# > 是否使垃圾收集器的可用内存比正常情况下更快?MyCollection

是否使垃圾收集器的可用内存比正常情况下更快?MyCollection

上一篇 下一篇

网友问答:
该接口的“主要”用途是清理非托管资源。
IDisposable

对我来说,“非托管”意味着数据库连接、套接字、窗口句柄等。但是,我已经看到了实现该方法以释放托管资源的代码,这对我来说似乎是多余的,因为垃圾收集器应该为您处理这个问题。Dispose()

例如:

public class MyCollection : IDisposable
{
    private List<String> _theList = new List<String>();
    private Dictionary<String, Point> _theDict = new Dictionary<String, Point>();

    // Die, clear it up! (free unmanaged resources)
    public void Dispose()
    {
        _theList.clear();
        _theDict.clear();
        _theList = null;
        _theDict = null;
    }
}

我的问题是,这是否使垃圾收集器的可用内存比正常情况下更快?MyCollection


编辑:到目前为止,人们已经发布了一些用于清理非托管资源(如数据库连接和位图)的好例子。但是假设在上面的代码中包含一百万个字符串,并且您希望现在释放该内存,而不是等待垃圾回收器。上面的代码会做到这一点吗?IDisposable_theList

分割线

网友回答:

IDisposable通常用于利用该语句,并利用一种简单的方法来对托管对象进行确定性清理。using

public class LoggingContext : IDisposable {
    public Finicky(string name) {
        Log.Write("Entering Log Context {0}", name);
        Log.Indent();
    }
    public void Dispose() {
        Log.Outdent();
    }

    public static void Main() {
        Log.Write("Some initial stuff.");
        try {
            using(new LoggingContext()) {
                Log.Write("Some stuff inside the context.");
                throw new Exception();
            }
        } catch {
            Log.Write("Man, that was a heavy exception caught from inside a child logging context!");
        } finally {
            Log.Write("Some final stuff.");
        }
    }
}

分割线

网友回答:

“释放”的要点释放非托管资源。它需要在某个时候完成,否则它们将永远不会被清理。垃圾回收器不知道如何调用类型的变量,它不知道是否需要调用。DeleteHandle()IntPtrDeleteHandle()

注意:什么是非托管资源?如果您在Microsoft .NET Framework中找到它:它是托管的。如果您自己去浏览 MSDN,它是不受管理的。您使用 P/Invoke 调用来摆脱 .NET Framework 中所有可用内容的舒适世界的任何内容都是非托管的 – 您现在负责清理它。

您创建的对象需要公开外部世界可以调用的某个方法,以便清理非托管资源。该方法可以随心所欲地命名:

public void Cleanup()

public void Shutdown()

但相反,此方法有一个标准化的名称:

public void Dispose()

甚至还创建了一个接口,它只有一个方法:IDisposable

public interface IDisposable
{
   void Dispose()
}

因此,您使对象公开接口,从而承诺已编写了单个方法来清理非托管资源:IDisposable

public void Dispose()
{
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
}

大功告成。除非你可以做得更好。


如果您的对象分配了一个 250MB 的 System.Drawing.Bitmap(即 .NET 管理的 Bitmap 类)作为某种帧缓冲区,该怎么办?当然,这是一个托管的 .NET 对象,垃圾回收器将释放它。但是你真的想留下250MB的内存坐在那里 – 等待垃圾收集器最终出现并释放它吗?如果存在打开的数据库连接怎么办?当然,我们不希望该连接处于打开状态,等待 GC 完成对象。

如果用户已经调用(意味着他们不再打算使用该对象),为什么不摆脱那些浪费的位图和数据库连接呢?Dispose()

所以现在我们将:

  • 摆脱非托管资源(因为我们必须这样做),以及
  • 摆脱托管资源(因为我们希望有所帮助)

因此,让我们更新我们的方法来摆脱这些托管对象:Dispose()

public void Dispose()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //Free managed resources too
   if (this.databaseConnection != null)
   {
      this.databaseConnection.Dispose();
      this.databaseConnection = null;
   }
   if (this.frameBufferImage != null)
   {
      this.frameBufferImage.Dispose();
      this.frameBufferImage = null;
   }
}

一切都很好,除了你可以做得更好


如果对方忘记调用您的对象怎么办?然后他们会泄漏一些不受管理的资源!Dispose()

注意:它们不会泄漏托管资源,因为垃圾回收器最终将在后台线程上运行,并释放与任何未使用对象关联的内存。这将包括您的对象以及您使用的任何托管对象(例如 和 )。BitmapDbConnection

如果这个人忘记打电话,我们仍然可以保存他们的培根!我们仍然有一种方法可以为它们调用它:当垃圾收集器最终开始释放(即最终确定)我们的对象时。Dispose()

注意:垃圾回收器最终将释放所有托管对象。
当它这样做时,它会在对象上调用 Finalize
方法。GC 不知道也不
关心您的处置方法。
这只是我们为想要
摆脱非托管内容时调用的方法选择
的名称。

垃圾回收器销毁我们的对象是释放那些讨厌的非托管资源的最佳时机。我们通过覆盖方法来实现此目的。Finalize()

注意:在 C# 中,不会显式重写该方法。
您编写了一个看起来像C++析构函数的方法,
编译器将其作为该方法的实现:
Finalize()Finalize()

~MyObject()
{
    //we're being finalized (i.e. destroyed), call Dispose in case the user forgot to
    Dispose(); //<--Warning: subtle bug! Keep reading!
}

但是该代码中存在一个错误。你看,垃圾回收器在后台线程上运行;您不知道两个对象的销毁顺序。完全有可能在您的代码中,您尝试删除的托管对象(因为您想提供帮助)不再存在:Dispose()

public void Dispose()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle);

   //Free managed resources too
   if (this.databaseConnection != null)
   {
      this.databaseConnection.Dispose(); //<-- crash, GC already destroyed it
      this.databaseConnection = null;
   }
   if (this.frameBufferImage != null)
   {
      this.frameBufferImage.Dispose(); //<-- crash, GC already destroyed it
      this.frameBufferImage = null;
   }
}

因此,您需要一种方法来告诉它不应该接触任何托管资源(因为它们可能不再存在),同时仍然释放非托管资源。Finalize()Dispose()

执行此操作的标准模式是具有并且两者都调用第三个(!)方法;如果你从(而不是)调用它,则传递布尔语句,这意味着释放托管资源是安全的。Finalize()Dispose()Dispose()Finalize()

这个内部方法可以给它一些任意的名字,如“CoreDispose”或“MyInternalDispose”,但传统上称之为:Dispose(Boolean)

protected void Dispose(Boolean disposing)

但更有用的参数名称可能是:

protected void Dispose(Boolean itIsSafeToAlsoFreeManagedObjects)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //Free managed resources too, but only if I'm being called from Dispose
   //(If I'm being called from Finalize then the objects might not exist
   //anymore
   if (itIsSafeToAlsoFreeManagedObjects)  
   {    
      if (this.databaseConnection != null)
      {
         this.databaseConnection.Dispose();
         this.databaseConnection = null;
      }
      if (this.frameBufferImage != null)
      {
         this.frameBufferImage.Dispose();
         this.frameBufferImage = null;
      }
   }
}

然后将该方法的实现更改为:IDisposable.Dispose()

public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it's safe
}

和您的终结器:

~MyObject()
{
   Dispose(false); //I am *not* calling you from Dispose, it's *not* safe
}

注意:如果你的对象是从实现的对象衍生而来的,那么当你重写Dispose时,不要忘记调用它们的基本Dispose方法:Dispose

public override void Dispose()
{
    try
    {
        Dispose(true); //true: safe to free managed resources
    }
    finally
    {
        base.Dispose();
    }
}

一切都很好,除了你可以做得更好


如果用户调用您的对象,则所有内容都已清理。稍后,当垃圾回收器出现并调用 Finalize 时,它将再次调用。Dispose()Dispose

这不仅是浪费,而且如果你的对象有垃圾引用,你已经从上次调用 到 ,你将尝试再次处置它们!Dispose()

您会注意到,在我的代码中,我小心翼翼地删除了对已释放对象的引用,因此我不会尝试调用垃圾对象引用。但这并没有阻止一个微妙的错误蔓延。Dispose

当用户调用 : 句柄 CursorFileBitmapIconServiceHandle 被销毁。稍后,当垃圾回收器运行时,它将尝试再次销毁相同的句柄。Dispose()

protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy 
   ...
}

解决此问题的方法是告诉垃圾回收器它不需要费心完成对象 – 其资源已被清理,不需要更多工作。您可以通过调用以下方法来执行此操作:GC.SuppressFinalize()Dispose()

public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it's safe
   GC.SuppressFinalize(this); //Hey, GC: don't bother calling finalize later
}

现在用户已经调用了 ,我们有:Dispose()

  • 释放的非托管资源
  • 释放的托管资源

GC 运行终结器是没有意义的——一切都得到了处理。

我无法使用 Finalize 来清理非托管资源吗?

的文档说:Object.Finalize

Finalize 方法用于在销毁对象之前对当前对象持有的非托管资源执行清理操作。

但 MSDN 文档还说:IDisposable.Dispose

执行与释放、释放或重置非托管资源相关的应用程序定义的任务。

那么到底是哪一个呢?哪一个是清理非托管资源的位置?答案是:

这是你的选择!但是选择 .Dispose

您当然可以将非托管清理放在终结器中:

~MyObject()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //A C# destructor automatically calls the destructor of its base class.
}

这样做的问题是你不知道垃圾收集器什么时候会完成你的对象。未托管、不需要、未使用的本机资源将一直存在,直到垃圾回收器最终运行。然后它将调用您的终结器方法;清理非托管资源。Object.Finalize 的文档指出了这一点:

终结器执行的确切时间未定义。若要确保确定性地释放类实例的资源,请实现 Close 方法或提供实现。IDisposable.Dispose

这是用于清理非托管资源的优点;您可以了解并控制何时清理非托管资源。它们的破坏是“确定性的”。Dispose


回答您最初的问题:为什么不现在释放内存,而不是在 GC 决定释放内存时释放内存?我有一个面部识别软件,现在需要摆脱 530 MB 的内部图像,因为它们不再需要。当我们不这样做时:机器会磨到交换停止。

奖金阅读

对于任何喜欢这个答案风格的人(解释为什么所以如何变得显而易见),我建议你阅读Don Box的基本COM的第一章:

  • 直接链接:皮尔逊出版社的第 1 章样本
  • 磁铁: 84BF0b960936d677190a2be355858e80ef7542c0

在35页中,他解释了使用二进制对象的问题,并在你眼前发明了COM。一旦你意识到COM的原因,剩下的300页是显而易见的,只是详细介绍微软的实现。

我认为每个曾经处理过对象或COM的程序员至少应该阅读第一章。这是有史以来最好的解释。

额外奖励阅读

当你知道的一切都是错误的存档埃里克·利珀特

因此,编写正确的终结器确实非常困难,
我能给你的最好建议是不要尝试

分割线

网友回答:

Dispose 模式的目的是提供一种机制来清理托管和非托管资源,何时清理取决于 Dispose 方法的调用方式。在您的示例中,使用 Dispose 实际上并没有执行与 Dispose 相关的任何操作,因为清除列表对正在释放的集合没有影响。同样,将变量设置为 null 的调用对 GC 也没有影响。

您可以查看本文以获取有关如何实现 Dispose 模式的更多详细信息,但它基本上如下所示:

public class SimpleCleanup : IDisposable
{
    // some fields that require cleanup
    private SafeHandle handle;
    private bool disposed = false; // to detect redundant calls

    public SimpleCleanup()
    {
        this.handle = /*...*/;
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Dispose managed resources.
                if (handle != null)
                {
                    handle.Dispose();
                }
            }

            // Dispose unmanaged managed resources.

            disposed = true;
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

这里最重要的方法是Dispose(bool),它实际上在两种不同的情况下运行:

  • 释放 == true:该方法已被用户的代码直接或间接调用。可以释放托管和非托管资源。
  • 释放 == false:运行时已从终结器内部调用该方法,不应引用其他对象。只能释放非托管资源。

简单地让 GC 负责清理的问题在于,您无法真正控制 GC 何时运行收集周期(您可以调用 GC。Collect(),但你真的不应该),所以资源可能会停留比需要的时间更长。请记住,调用 Dispose() 实际上不会导致收集周期或以任何方式导致 GC 收集/释放对象;它只是提供更确定地清理所用资源的方法,并告知 GC 此清理已执行。

IDisposable和释放模式的重点不是立即释放内存。对 Dispose 的调用实际上唯一有机会立即释放内存的情况是当它处理释放 == false 场景并操作非托管资源时。对于托管代码,在 GC 运行收集周期之前,实际上不会回收内存,您实际上无法控制(除了调用 GC。Collect(),我已经提到过这不是一个好主意)。

你的方案实际上无效,因为 .NET 中的字符串不使用任何未聚合的资源,也不实现 IDisposable,因此无法强制“清理它们”。

模板简介:该模板名称为【是否使垃圾收集器的可用内存比正常情况下更快?MyCollection】,大小是暂无信息,文档格式为.编程语言,推荐使用Sublime/Dreamweaver/HBuilder打开,作品中的图片,文字等数据均可修改,图片请在作品中选中图片替换即可,文字修改直接点击文字修改即可,您也可以新增或修改作品中的内容,该模板来自用户分享,如有侵权行为请联系网站客服处理。欢迎来懒人模板【C#】栏目查找您需要的精美模板。

相关搜索
  • 下载密码 lanrenmb
  • 下载次数 206次
  • 使用软件 Sublime/Dreamweaver/HBuilder
  • 文件格式 编程语言
  • 文件大小 暂无信息
  • 上传时间 02-08
  • 作者 网友投稿
  • 肖像权 人物画像及字体仅供参考
栏目分类 更多 >
热门推荐 更多 >
微信公众平台 微信模板 微信图片 响应式 微信素材 企业网站 单页式简历模板 微信文章 自适应 html5
您可能会喜欢的其他模板