C#语言:垃圾收集、SuppressFinalize
我正在阅读《The C# Language》,第 4 版,它谈到了垃圾收集,如下所示:
“BILL WAGNER:以下规则是 C# 和其他托管环境之间的重要区别。
在应用程序终止之前,将调用其所有尚未被垃圾收集的对象的析构函数,除非此类清理已被抑制(例如,通过调用库方法 GC.SuppressFinalize)。”
所以我有几个问题:
Q1。为什么 .net 与其他托管环境不同(我想这在暗示 Java?)?有什么特别的设计问题吗?
第二季度。调用
GC.SuppressFinalize
的对象会发生什么情况? 我理解这意味着GC不会调用此类对象的终结器(析构函数),如果是这样,这些对象什么时候会真正被销毁,以便分配的内存位返回到堆?否则会出现内存泄漏吗?
I'm reading "The C# Language", 4th Edition, it talks about garbage collection as below:
"BILL WAGNER: The following rule is an important difference between C# and other managed environments.
Prior to an application’s termination, destructor's for all of its objects that have not yet been garbage collected are called, unless such cleanup has been suppressed (by a call to the library method GC.SuppressFinalize, for example)."
So I have a couple of questions here:
Q1. Why .net is different from other managed environments (I suppose this is hinting Java?) here? Any particular design concern?
Q2. What will happened to objects that
GC.SuppressFinalize
is called?
I understand that this means GC will not call such objects' finalizer (destructor), if so, when will these objects got really destroyed, so that the allocated memory bits are returned to the heap? Otherwise there'll be Memory Leak?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
您对最终化的用途存在误解。终结是为了清理非托管内存的资源。
假设您有一个包含整数字段的引用类型对象。该整数字段恰好是通过调用非托管代码打开文件而获得的文件句柄。
由于某些其他程序可能想要访问该文件,因此应尽快关闭该文件。但.NET 运行时并不知道这个整数对操作系统有任何特殊含义。它只是一个整数。
解决此问题的方法通常是将对象标记为实现 IDisposable,然后在处理完该对象后立即调用该对象的“Dispose”。然后,您执行的“Dispose”将关闭该文件。
请注意,这里没有什么特别的事情发生。只是一个惯例,清理非托管资源的方法称为“Dispose”,需要处置的对象实现 IDisposable。垃圾收集对此一无所知。
那么现在问题来了:如果有人忘记调用Dispose怎么办?文件是否永远保持打开状态? (显然,当进程结束时文件将被关闭,但是如果进程运行很长时间怎么办?)
为了解决这个问题,您可以使用终结器。这是如何运作的?
当一个对象即将被垃圾收集时,垃圾收集器会检查它是否有终结器。如果是,则不会对其进行垃圾回收,而是将其放入终结器队列中。在未来的某个未指定的时刻,一个线程会运行来检查队列并对每个对象调用特殊的“Finalize”方法。之后,该对象将从终结队列中删除并标记为“嘿,我已经被终结了”。该对象现在再次符合收集条件,因此垃圾收集器最终运行并收集该对象,而不将其放入终结队列中。
显然“Finalize”和“Dispose”经常需要做同样的事情。
但现在又出现了另一个问题。假设您处置一个对象。 现在不需要最终确定。最终确定的成本很高;它使死去的物体存活的时间比其需要的时间长得多。因此,传统上,当人们释放一个对象时,Dispose的实现不仅会关闭非托管资源,还会将该对象标记为“该对象已被终结,不要再次终结”。这样它就会欺骗垃圾收集器不将对象放入终结队列中。
那么让我们回答您的具体问题:
当对象死亡时,垃圾收集器将简单地回收该对象的内存,而不将该对象放入终结器队列中。
GC 从不调用终结器。终结器线程是唯一调用终结器的线程。
目前尚不清楚“破坏”是什么意思。如果你的意思是“终结器什么时候运行?”答案是“从不”,因为你说要抑制最终确定。如果你的意思是“托管堆中的内存什么时候会被回收?”,答案是“一旦对象被垃圾收集器识别为死亡”。这种情况会比正常情况发生得更快,因为终结器队列不会让对象保持活动状态。
You have a misunderstanding of what finalization is for. Finalization is for cleaning up resources that are not managed memory.
Suppose you have an object of reference type that contains an integer field. That integer field just happens to be a handle to a file that was obtained by calling into unmanaged code to open the file.
Since some other program might want to access that file, it is polite to close the file as soon as possible. But the .NET runtime has no idea that this integer has any special meaning to the operating system. It's just an integer.
The way you solve this problem typically is you mark the object as implementing IDisposable, and then you call "Dispose" on the object as soon as you're done with it. Your implementation of "Dispose" then closes the file.
Note that there is nothing special going on here. It is just a convention that a method that cleans up an unmanaged resource is called "Dispose" and an object that needs to be disposed implements IDisposable. The garbage collection knows absolutely nothing about this.
So now the problem arises: what if someone forgets to call Dispose? Does the file stay open forever? (Clearly the file will be closed when the process ends, but what if the process runs for a long time?)
To solve this problem, you use a finalizer. How does that work?
When an object is about to be garbage collected, the garbage collector checks it to see if it has a finalizer. If it does, then instead of garbage collecting it, it puts it on the finalizer queue. At some unspecified point in the future, a thread runs that examines the queue and calls a special "Finalize" method on every object. After that, the object is removed from the finalization queue and marked as "hey, I've already been finalized". The object is now once again eligable for collection, and so the garbage collector eventually runs and collects the object without putting it on the finalization queue.
Clearly "Finalize" and "Dispose" frequently need to do the same thing.
But now another problem arises. Suppose you dispose an object. Now it does not need to be finalized. Finalization is expensive; it keeps a dead object alive for much longer than it needs to be. Therefore, traditionally when one disposes an object, the implementation of Dispose not only closes the unmanaged resource, it also marks the object as "this object has already been finalized, don't finalize it again". That way it tricks the garbage collector into not putting the object on the finalization queue.
So let's answer your specific questions:
When the object is dead the garbage collector will simply reclaim the memory of the object without putting the object on the finalizer queue.
The GC never calls a finalizer. The finalizer thread is the only thing that calls finalizers.
It is not clear what you mean by "destructed". If you mean "when will the finalizers run?" the answer is "never" because you said to suppress finalization. If you mean "when will the memory in the managed heap be reclaimed?", the answer is "as soon as the object is identified as dead by the garbage collector". That will happen sooner than normal because the object will not be kept alive by the finalizer queue.
Q1:我怀疑这是因为它更关心实现出色的性能,而不是 Java 为了简单性而做出了相当多的牺牲。
Q2:由于终结器甚至不能保证首先被调用(即使
SuppressFinalize
不存在),因此当您已经处置了资源时,这应该仅用于性能原因。否则,您应该使用IDisposable
来处置资源。Finalization != Destruction
析构函数(在 C++ 意义上)在 .NET 中不存在——因为从某种意义上说,每个对象都有一个“析构函数”,称为垃圾收集器。 :)
C# 所谓的“析构函数”实际上是终结器。
终结器用于处理除对象分配的内存之外的其他事物,例如文件句柄等。因此,并非每个对象都有终结器。内存始终由 GC 释放,因此不会出现内存泄漏。
Q1: I suspect it's because it cares more about achieving great performance, as opposed to Java which has made quite a few sacrifices for simplicity.
Q2: Since finalizers aren't even guaranteed to be called in the first place (even if
SuppressFinalize
didn't exist), this should be only used for performance reasons, when you've disposed the resources already. Otherwise you should useIDisposable
to dispose of resources.Finalization != Destruction
Destructors (in the C++ sense) don't exist in .NET -- because every object, in a sense, has a "destructor", called the garbage collector. :)
What C# calls "destructors" are in fact finalizers.
Finalizers are for disposing things other than the memory allocated by the object, such as file handles, etc. Not every object, therefore, had a finalizer. The memory is always freed by the GC, so you don't get a memory leak that way.
我只知道第二个答案:
SuppressFinalize
当对象已经被破坏时调用,即通过IDisposable.Dispose
。因此它不应该再次被破坏。这是因为终结器是在不确定时间发生的资源清理。添加了
IDisposable
,以允许在特定的、可预测的时间进行资源清理。编辑:在进程终止时,内存泄漏并不重要。当进程终止时,Windows 会收集其堆,因此对象的内存是否返回到堆并不重要。
I only know the second answer:
SuppressFinalize
is called when the object has already been destructed, i.e., byIDisposable.Dispose
. So it shouldn't be destructed again.This is because finalizers are a resource cleanup that occurs at a non-deterministic time.
IDisposable
was added to allow resource cleanup that occurs at a specific, predictable time.EDIT: At process termination, memory leaks are immaterial. When a process terminates, its heaps are collected by Windows, so it doesn't matter if an object's memory is returned to the heap or not.
我尝试回答问题2:
如果你的类有一个终结器(由~ClassName所示),那么它不会直接被垃圾收集器收集,而是会被放入终结器队列中,稍后会被调用。现在,当最终调用终结器时,它通常会释放创建的类的所有非托管资源。如果出于某种原因用户已经这样做了(通常是通过调用 dispose),则终结器不需要清理非托管内存,因此需要调用 SuppressFinalizer。否则它会再次尝试释放资源。
因此,您将获得的唯一内存泄漏是您在创建类时请求的任何资源。终结器只是释放框架尚未管理的所有内容。
I try to answer Q2:
If your class has a finalizer (shown by the ~ClassName), then it will not be directly collected by the garbage collection, instead it will be put on the finalizer queue which will be called later. Now when finalizer is finally called it usually frees any unmanaged resources, the class created. If for any reason the user already did it, usually by calling dispose, the finalizer don't need to clean the unmanaged memory, thus calling SuppressFinalizer is necessary. Otherwise it would try to free the resources again.
So the only memory leak you will get is by any resources that you requested on creation of your class. The finalizer is just to free everything that is not already managed by the framework.
有因为一旦调用GC.Collect()
最终确定的对象将至少移动一次到下一代。
我们可以调用GC.SuppressFinalize,GC就会知道这个对象被解除引用,然后我们就可以删除这个对象和紧凑堆。
通常,该实现具有 dispose 模式
There is because once call GC.Collect()
The object with finalize will move to next generation at least once.
We can call GC.SuppressFinalize, and GC will know this object is de-reference, we can remove this object and compact heap then.
Usually, this implement with dispose pattern