【问题标题】:doubts regarding Memory management in .net关于 .net 中的内存管理的疑问
【发布时间】:2010-12-13 21:05:40
【问题描述】:

我正在从《Professional C#》一书中学习 C# 中的内存管理

垃圾收集器的存在 意味着你通常不会担心 关于不再需要的物品; 您将简单地允许所有引用 那些对象超出范围 并允许垃圾收集器 根据需要释放内存。然而 垃圾收集器不知道如何 释放非托管资源(例如文件 句柄、网络连接和 数据库连接)。管理时 类封装直接或间接 对非托管资源的引用,您 需要作出特别规定 确保非托管资源 当一个实例被释放 类被垃圾收集。

定义类时,可以使用 自动释放的两种机制 非托管资源。

  1. 将析构函数(或终结器)声明为类的成员。
  2. 在您的系统中实现 System.IDisposable 接口 类。

我没明白几件事:

  1. “非托管资源(例如文件句柄、网络连接和数据库连接)”。他们有什么大不了的?他们怎么没有管理? (或)为什么 GC 不能管理这些资源?

  2. 我们将在 a 类的 finalizer 或 Dispose() 方法中放置什么代码,该代码究竟是什么样的?使用这些资源的一些示例会很有帮助。

【问题讨论】:

  • 另一个问题已结束。
  • 感谢关闭其他线程。只有模组可以关闭问题吗?
  • @claws 请参阅 stackoverflow.com/faq 您需要 3000 次代表投票才能结束或打开他人的问题,需要 250 次代表才能投票结束您自己的问题。

标签: c# .net memory-management garbage-collection


【解决方案1】:

.NET 框架上的某些类只是 Windows API 或第三方程序集的包装器。这些 API 不是托管代码(它们可以用 C++ 编写或者它们是旧的 COM 程序集),并且垃圾收集器不知道应用程序何时不再需要它们。

例如,当您打开磁盘文件时,它将保持打开状态,直到您告诉它关闭该文件。如果您销毁指向文件的指针(即离开作用域)而不关闭文件,则该文件将保持打开和锁定状态。

在框架上为这些类实现的 Dispose 方法调用内部 Close 方法,以干净的方式完成实例。所以包装非托管代码的所有类都应该实现 Disposable 接口,以确保实现它的关闭方法。

然后,当您实例化该类时,最好使用 using 语句执行此操作,因为当您离开作用域时,会自动调用 Dispose 方法。

【讨论】:

  • 这是有道理的。我总是从内存泄漏的角度来看 GC。所以,它只是关于记忆。您也不想让文件或与数据库的连接保持打开状态。这些应该手动关闭。正确的?如果文件未关闭且对象超出范围,甚至应用程序也退出。仍然无法删除或移动此文件,因为操作系统提示“无法将文件移动/删除/修改/重命名为正在使用的文件”。但事实是该进程不再运行。我经历过几次。我必须重新启动/重新登录才能解决此问题。
  • 您可以使用 Unlocker (ccollomb.free.fr/unlocker) 等应用程序来修复锁定文件的问题。
【解决方案2】:

这里真正的问题是关于紧迫性。由于垃圾收集器显式跟踪内存,它会知道何时需要通过清理未引用的对象来释放内存。这可能每分钟发生几次,或每小时发生一次,甚至永远不会发生(如果不需要创建新对象)。但重要的是,它确实会在需要时发生。

但内存并不是唯一受限的资源。拿文件。通常一次只有一个应用程序可以打开一个文件,因为如果几个人尝试写入同一个文件,它可能会变得混乱。数据库的连接数量有限。等等。垃圾收集器不跟踪任何这些资源。而且它不知道关闭它们有多紧迫。

当然,您可以打开 FileStream 并从中读取,而无需在之后关闭它。如果您取消对对象的引用,最终垃圾收集器可能会决定收集 FileStream 对象,该对象将运行其 Finalizer 并且文件将正确关闭。但这可能需要很长时间,同时文件被锁定。

对于数据库连接来说,它更加紧迫,因为可用的集合数量非常有限,所以如果你打开太多连接而不释放它们,你最终会得到一个错误,因为你会有一堆数据库对象有打开在垃圾收集器队列中等待的连接。

因此,正确处理 Disposable 对象是一种很好的做法。有时你可以不这样做而逃脱,但这是一种糟糕的风格。如果一个对象实现了 IDisposable,那是因为它希望你在用完它后清理它。

【讨论】:

    【解决方案3】:

    1.) GC 不知道如何正确关闭外部资源。当然,他可以终止网络连接(实际上,如果您不断开连接,即数据库连接,他会这样做)。但是数据库没有收到关闭连接的通知。

    文件流也是如此。缓冲区里还有东西吗?在关闭文件句柄之前是否必须将其写入文件? GC 不知道这一点 - 访问代码知道。

    2.) 是由此而来的。 因此,如果您有打开的文件流和内部缓冲区 - 在 dispose 方法中,您将刷新缓冲区,将其写入文件并关闭文件 hanlde。

    通常,您不直接访问数据库。您使用图书馆为您管理。

    在大多数情况下,如果您的类正在被释放,那么释放那些外部资源管理器(数据库连接、文件流、网络类)就足够了。

    【讨论】:

      【解决方案4】:

      这是一个很好的问题,但许多开发人员似乎并不理解。

      在较高级别上,托管资源是由 .Net 分配和跟踪的资源。资源使用的内存来自分配给 .Net 的池,.Net 运行时跟踪托管资源之间的所有引用。这种跟踪(我确信这是一个错误的术语,但在这里就足够了)允许 .Net 运行时知道给定资源何时不再被使用并因此有资格被释放。因此,非托管资源是在该 .Net 托管池之外分配的资源,并且不被运行时跟踪。大多数情况下,这些是对操作系统或外部应用程序资源的引用。 .Net 运行时无法“看到”非托管资源有各种复杂的原因,但我喜欢这样想:.Net 是一个有围墙的开发花园,您必须进入才能使用。您可以在那堵墙上戳一个洞以查看外面(即 PInvoke),但您不能拥有另一边的资源。

      现在,进入问题的第二部分。 Bill Wagner 在他的书Effective C# 中对如何实现 Dispose 方法以及为什么进行了很好的讨论。关于这个herehere,也有一些非常好的答案。

      希望这会有所帮助。

      【讨论】:

        【解决方案5】:

        【讨论】:

          【解决方案6】:

          非托管资源是操作系统拥有和控制的资源的句柄(当然内存除外)。

          GC 不会在不再有任何对对象的引用时立即清理内存 - 它可能会离开很长时间。如果它对文件、网络和图形句柄执行此操作,则可能会占用大量操作资源,并且只是偶尔释放它们。

          为了将这些非托管资源释放回操作系统,您需要通过释放它们来显式释放它们。因此使用 IDisposable 和 using 关键字。

          【讨论】:

            【解决方案7】:

            我不喜欢引用的文本使用术语“非托管资源”的方式,因为它表明该术语主要指的是操作系统知道的对象。事实上,我认为将“非托管资源”视为当前对象之外的东西(可能在计算机之外!)更有帮助,其使用寿命可能超过当前对象的使用寿命,其状态可能已更改以一种如果不清理会导致问题的方式,并且当前对象应该清理它。 “托管资源”是对包含一个或多个“非托管资源”的对象的引用,但即使这些资源被废弃,它通常也会设法处理这些资源(至少最终)。

            即使在完全托管的代码中也可能有非托管资源。举个简单的例子,一个集合的枚举器可能会订阅一个事件,所以当集合发生变化时它会得到通知。集合的事件订阅列表是非托管资源。除非枚举器在被放弃之前取消订阅事件,否则持有事件订阅的集合的使用寿命很可能会超过枚举器的使用寿命。虽然偶尔放弃的事件订阅可能不会造成太大的伤害,但创建许多枚举器并在不清理订阅的情况下放弃它们的例程可能会造成严重破坏。

            【讨论】:

              【解决方案8】:

              在实践中,我使用本地代码 - C++ - 也称为非托管和托管代码 - C# 进行了大量编码。我仍然不确定为什么首先发明了 C# - 当然对开发人员有很多改进,但 C# 架构背后隐藏着很多困难。

              据我所知,许多 Microsoft 专业开发人员都接到了让 C# 成为现实的任务,并且就像任何新平台通常一样 - 开发人员对他们的技术过度兴奋。

              第一次尝试当然是声称“我们在做正确的事情”,而其他人都做错了——我猜“非托管”一词就是因为这个而出现的。就像我们这里有“托管”代码和一些设计不正确的东西——“un”——一些东西。 :-)

              文件句柄、网络连接、数据库连接等非托管资源已经被管理了很长时间 - 如果您终止进程,它将关闭所有文件句柄。

              在 C++ 上,你有 malloc、free,在 C# 上,你有 new(或 gcnew),但是你正在与什么引用什么、为什么这个对象不会从内存中消失、什么在吃 ram 等问题作斗争 -并且这些问题的大部分答案都变得很难回答。

              构造函数/析构函数被终结器、析构函数、一次性对象所取代,在这些函数中测试调用的内容、调用顺序相对困难,您还记得释放所有资源吗? “哦,我们被管理了,但我们不知道如何管理这些对象......”:)

              只要应用程序小而简单,C# 就很有趣——在你添加 3d 对象、超大量的分配、大量功能、编译开始变慢之后,你不再对 C# 感到满意并考虑回到 C++。

              在相当多的论坛上,您可能会发现一些关于 C# 或 C++ 更好/更快/更容易的争论,并且大多数人试图不惜一切代价保护使用 C#。现实是它过于复杂、过于抽象和过于沉重的怪物,已经没有人能控制它了。

              中间语言 (IL) - 代表源代码和可执行代码(程序集)之间的一个额外抽象层,这使得优化和提升您的程序变得更加困难。

              但我也不是 C++ 的忠实粉丝 - 指针、引用和对象/类本身的语言复杂性并不能使 C++ 更容易编码或更容易学习。

              基本上,当您构建新语言时 - 您需要考虑目标语言基础架构(低级汇编 + 与 C++ 代码的平滑集成和高级代码改进 - 易于使用、易于理解、易于开发、易于维护,并改进)。

              理论上创造更多的“词”可能会使语言更丰富——你可以用更少的文字更有效地表达自己,但这并不能防止语言本身的污染(如 IDisposable)。

              我有意将编程语言与自然语言进行比较,因为我现在正在用自然语言编写答案,而且它比编程语言更适合我。然而,编程语言的结构比自然语言更好,并且没有 2016 年的历史。

              2 - finalizer / dispose - 本章至少阅读了 5 遍,但仍然不明白。我通常创建一个函数(关闭)并从两个函数(从终结器)和 dispose 调用它。为什么要费心去理解一些不重要的东西。

              无论如何,我对您的建议是 - 尝试代码中的所有内容 - 它看起来如何,感觉如何。书籍往往会变成类似于圣经的东西——它们会把你拉入宗教,而你并不一定要参与其中。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2016-03-26
                • 2011-12-21
                • 1970-01-01
                • 2020-12-22
                相关资源
                最近更新 更多