关于c#:关于.net中内存管理的疑问

doubts regarding Memory management in .net

我正在从《专业C》一书中学习C中的记忆管理。

The presence of the garbage collector
means that you will usually not worry
about objects that you no longer need;
you will simply allow all references
to those objects to go out of scope
and allow the garbage collector to
free memory as required. However, the
garbage collector does not know how to
free unmanaged resources (such as file
handles, network connections, and
database connections). When managed
classes encapsulate direct or indirect
references to unmanaged resources, you
need to make special provision to
ensure that the unmanaged resources
are released when an instance of the
class is garbage collected.

When defining a class, you can use
two mechanisms to automate the freeing
of unmanaged resources.

  • Declaring a destructor (or finalizer) as a member of your class.
  • Implementing the System.IDisposable interface in your
    class.
  • 我什么都不懂:

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

  • 我们将在类的finalizer或dispose()方法中放置什么代码,以及该代码到底是什么样的?使用这些资源的一些例子会有很大的帮助。


  • .NET框架上的一些类只是Windows API或第三方程序集的包装器。这些API不是托管代码(它们可以用C++编写,也可以是旧的COM组件),垃圾回收器不知道应用程序不再需要什么时候。

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

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

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


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

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

    当然,您可以打开一个文件流并从中读取,而不必随后关闭它。如果对对象的引用为空,垃圾收集器最终可能会决定收集文件流对象,该对象将运行其终结器,文件将正确关闭。但这可能需要很长时间,同时文件被锁定。

    对于数据库连接来说,情况更为紧急,因为可用的集合数量非常有限,因此如果打开的连接太多而不进行处理,最终会出现错误,因为会有一组数据库对象具有打开的连接,这些连接在垃圾收集器队列中等待处理。

    因此,正确处理一次性物品是良好的做法。有时候你不这么做就可以脱身,但这是一种糟糕的风格。如果一个对象实现了IDisposable,那是因为它希望您在使用完它之后对其进行清理。


    1.)GC不知道如何正确关闭外部资源。当然,他可以终止一个网络连接(事实上,如果不断开连接,即数据库连接,他会做什么)。但是没有通知数据库关闭连接。

    文件流也有类似的情况。缓冲器里还有什么东西吗?关闭文件句柄之前是否必须将其写入文件?GC不知道这一点-访问代码知道。

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

    通常,您不会直接访问数据库。您可以使用库来为您管理这个。

    在大多数情况下,如果您的类正在被释放,就可以释放这些外部资源管理器(DB连接、文件流、网络类)。


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

    从高层来看,托管资源是由.NET分配和跟踪的资源。资源使用的内存来自分配给.NET的池,.NET运行时跟踪托管资源之间的所有引用。此跟踪(我确信这是错误的术语,但在这里就足够了)允许.NET运行时知道给定资源何时不再被使用,从而有资格被释放。因此,非托管资源是在该.NET托管池之外分配的资源,运行时不跟踪这些资源。通常,这些都是对操作系统或外部应用程序资源的引用。有各种复杂的原因导致.NET运行时无法"看到"非托管资源,但我喜欢这样想:.NET是一个有围墙的开发花园,您必须进入它才能使用。你可以在那面墙上戳一个洞看外面(比如,宾沃克),但是你不能在另一边拥有一个资源。

    现在,进入问题的第二部分。比尔·瓦格纳在他的书《有效的C》中对如何实施处置方法以及为什么要这样做进行了大量的讨论。这里和这里也有一些很好的答案。

    希望这有帮助。


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

    即使在完全托管的代码中,也有可能拥有非托管资源。作为一个简单的例子,集合的枚举器可能订阅一个事件,因此如果集合发生更改,它将得到通知。集合的事件订阅列表是非托管资源。除非枚举器在放弃事件之前取消订阅,否则包含事件订阅的集合的使用寿命可能会超过枚举器的使用寿命。虽然偶尔放弃的事件订阅可能不会造成太大的伤害,但是一个创建许多枚举器并在不清除订阅的情况下放弃它们的例程可能会造成严重的破坏。


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

    GC不会在不再有任何对象引用的时候立即清理内存,这可能会使它保留很长一段时间。如果对文件、网络和图形进行处理,可能会占用大量的操作资源,并且只会偶尔释放它们。

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


    有关2)请参见:http://msdn.microsoft.com/en-us/library/fs2xkftw.aspx


    在实践中,我做了大量的编码,同时使用了本机代码-C++,也被称为非托管代码和托管代码C。我仍然不知道为什么C首先被发明了-当然,开发人员有很多改进,但C架构背后有很多隐藏的石头。

    据我所知,许多微软专业开发人员接受了一项任务,使C成为现实,就像通常与任何新平台一起使用一样,开发人员对他们的技术过于兴奋。

    第一次尝试当然是宣称"我们做的是正确的事情",而其他人做的都是错误的——我想"未管理"一词就是因为这个出现的。就像我们这里有"托管"代码和一些设计不正确的东西——"un"——一些东西。-)

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

    在C++上,你有MALOC,免费,在C语言上你有新的(或者GCNEX),但是你正在与什么东西有关,为什么这个对象不会离开内存,什么是吃RAM,并且大多数问题的答案变得很难回答。

    构造函数/析构函数被终结器、析构函数、可释放对象替换,在这些对象中,测试调用的内容比较困难,调用顺序是什么,您是否记得释放所有资源?"哦,我们是被管理的,但我们不知道如何管理这些对象……:)

    C是很有趣的,只要它是简单而简单的应用程序——在你添加了3D对象之后,还有大量的分配,大量的功能,编译开始变得缓慢,而且你不再满足于C,并考虑回到C++。

    在很多论坛上,你可能会发现一些关于-C或C++更好/更快/更容易的争论,而且大多数人都试图不惜一切代价来保护C。现实是,它过于复杂、过于抽象、过于沉重,再也没有人能控制它了。

    中间语言(IL)-表示源代码和可执行代码(汇编)之间的一个额外的抽象层,这使得优化和增强程序更加困难。

    但是,我并不是C++的大粉丝——语言复杂性与指针、引用和对象/类本身并不能使C++更容易代码化或更容易学习。

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

    在理论上创造更多的"词语"可能会使语言更加丰富——你可以用更少的文本来表达自己,而且效率更高,但这同样不能防止语言本身的污染(如IDisposable)。

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

    2-终结器/处置-已阅读本章至少5次,但仍不理解。我通常创建一个函数(关闭),并从两个函数(从终结器)和释放调用它。为什么要费心去理解一些不重要的事情。

    我给你的建议是——用代码来尝试一切——它看起来怎么样,感觉怎么样。书籍往往会变成类似于圣经的东西——它们把你拉入宗教,而你不一定想加入宗教。