区分实现的对象很重要IDisposable
以及带有终结器的对象。在大多数情况下(可能最好是所有情况),带有终结器的对象也实现IDisposable
但实际上它们是两个不同的东西,而且最常一起使用。
终结器是一种向 .NET 运行时告知在收集对象之前必须执行终结器的机制。当 .NET 运行时检测到某个对象符合垃圾回收条件时,就会发生这种情况。通常情况下,如果对象没有终结器,则会在本次收集期间将其收集。如果它有终结器,它将被放置到一个列表中,即“易碎队列”,并且有一个后台线程监视该线程。有时,在集合将对象放入此队列后,终结器线程将处理此队列中的对象并调用终结器方法。
一旦发生这种情况,该对象再次符合收集条件,但它也被标记为finalized,这意味着当垃圾收集器在未来的收集周期中找到该对象时,它不再将其放入此队列中,而是正常收集它。
请注意,在上面的文字段落中,IDisposable
一次都没有被提及,这是有充分理由的。以上均不依赖于IDisposable
at all.
现在,对象实现IDisposable
可能有也可能没有终结器。一般规则是,如果对象本身拥有非托管资源,它可能应该拥有,如果不拥有,它可能不应该拥有。(我在这里犹豫是否要说“总是”和“从不”,因为似乎总是有人能够找到一个角落案例,以某种方式有意义,但打破了“通常”规则)
A TL;DR上面的总结可能是,终结器是一种在收集对象时(半)保证清理对象的方法,但具体何时发生并不直接由程序员控制,而实现IDisposable
是直接从代码控制此清理的一种方法。
不管怎样,有了这些,让我们来解决您的具体问题:
当 C# .NET 4.5(或更高版本)应用程序关闭时,未正确处置的 IDisposables 会发生什么情况?
Answer:没有什么。如果它们有终结器,则终结器线程将尝试拾取它们,因为当程序终止时,all对象有资格被收集。然而,不允许终结器线程“永远”运行来执行此操作,因此它也可能会耗尽时间。另一方面,如果对象实现IDisposable
没有终结器,它只会被正常收集(同样,IDisposable
与垃圾收集完全没有关系)。
但假设我有一个控制台应用程序,带有静态 Stream 变量。当我关闭控制台应用程序时它会被释放吗?
Answer:不,不会的disposed. Stream
它本身是一个基类,因此根据具体的派生类,它可能有也可能没有终结器。然而,它遵循与上面相同的规则,因此如果它没有终结器,它将被简单地收集。例子,内存流没有终结器,而文件流 does.
HttpClient 怎么样?你怎么知道在哪些情况下它会起作用,在哪些情况下不会
Answer: The HttpClient 的参考源似乎表明HttpClient
没有终结器。因此,它将被简单地收集。
好吧,现在一些实际的背景信息。我经常将某些 IDisposable 存储为字段,强制我的类实现 IDisposable。最终用户应该使用 using。但如果这没有发生怎么办?
Answer:如果您忘记/没有打电话IDisposable.Dispose()
关于实现的对象IDisposable
,一旦对象符合收集条件,我在这里所说的有关终结器的所有内容仍然会发生。除此之外,不会发生什么特别的事情。对象是否实现IDisposable
或不与垃圾收集过程无关,只有终结器的存在才有。
GC 之前是否只是不必要的内存?或者你突然内存泄漏
Answer:从这个简单的信息中无法确定。这取决于什么Dispose
方法就可以了。例如,如果对象已在某处注册了自身,以便在某处存在对它的引用,则某些停止使用该对象的代码实际上可能不会使该对象符合收集条件。这Dispose
方法可能负责取消注册它,删除对它的最后一个引用。所以这个要看对象。仅仅因为该对象实现了IDisposable
不会造成内存泄漏。如果删除了对该对象的最后一个引用,则该对象将符合收集条件,并将在未来的收集周期中被收集。
Remarks:
-
Note that the above text is also probably simplified. A full collection cycle to actually "collect memory" is probably not done on application termination as there is no point. The operating system will free the memory allocated by the process when it terminates anyway. When an application terminates, .NET Framework makes every reasonable effort to call finalizers for objects that haven't yet been garbage collected, unless such cleanup has been suppressed (by a call to the library method GC.SuppressFinalize, for example). .NET 5 (including .NET Core) and later versions don't call finalizers as part of application termination.1 (I have no additional knowledge one way or another what kind of optimizations is done here)
-
这里更重要的部分是您需要区分内存(或其他)泄漏during程序执行和after程序执行
- 当进程终止时,操作系统将回收分配给它的所有内存,它将关闭所有句柄(这可能会保持套接字、文件等打开),所有线程将被终止。简而言之,程序被完全从内存中删除
- 不过,这个过程可能会留下一些花絮,它们是not除非进程事先小心地这样做了,否则会被清理。如上所述,打开的文件被关闭,但它可能尚未完全写入,因此可能以某种方式损坏。
- 在程序执行期间,泄漏可能会使程序在分配的内存方面增长,它可能会分配太多句柄,因为它无法关闭不再需要的句柄,等等,这对于处理而言很重要
IDisposable
和终结器正确,但是当进程终止时,这不再是问题。