C# GC.Collect()并完成
好的,众所周知,当GC将对象标识为垃圾时,它隐式地调用对象上的C# GC.Collect()并完成,c#,.net,garbage-collection,finalizer,C#,.net,Garbage Collection,Finalizer,好的,众所周知,当GC将对象标识为垃圾时,它隐式地调用对象上的Finalize方法。但是如果我执行一个GC.Collect(),会发生什么呢?终结器是否仍在执行?也许是个愚蠢的问题,但有人问我这个问题,我回答“是”,然后我想:“这完全正确吗?”是的,但不是马上。本节选自(*) 当应用程序创建一个新对象时,新操作员分配 堆中的内存。如果对象的类型包含Finalize 方法,然后将指向该对象的指针放置在终结点上 最终确定队列是一个内部数据结构,受控制 队列中的每个条目都指向一个对象 应该在对象的内存
Finalize
方法。但是如果我执行一个GC.Collect()
,会发生什么呢?终结器是否仍在执行?也许是个愚蠢的问题,但有人问我这个问题,我回答“是”,然后我想:“这完全正确吗?”是的,但不是马上。本节选自(*)
当应用程序创建一个新对象时,新操作员分配
堆中的内存。如果对象的类型包含Finalize
方法,然后将指向该对象的指针放置在终结点上
最终确定队列是一个内部数据结构,受控制
队列中的每个条目都指向一个对象
应该在对象的内存之前调用Finalize方法
可以回收
当GC发生时…垃圾收集器扫描终结
队列查找指向这些对象的指针。当找到指针时,
指针将从终结队列中删除并附加到
freachable队列(发音为“F-reachable”)。freachable队列为
垃圾收集器控制的另一个内部数据结构。
freachable队列中的每个指针都标识一个
准备好调用它的Finalize方法
有一个专用于调用Finalize的特殊运行时线程
方法。当freachable队列为空时(通常为
case),该线程处于休眠状态。但当条目出现时,该线程将被唤醒,
从队列中删除每个条目,并调用每个对象的Finalize
因此,您不应该在Finalize中执行任何代码
方法,该方法对执行
例如,避免访问
最终确定方法。”
(*)从2000年11月开始,所以从那时起情况可能已经发生了变化。事实上答案是“视情况而定”。实际上,有一个专用线程执行所有终结器。这意味着对GC.Collect
的调用只触发了这个过程,所有终结器的执行都将被异步调用
如果要等待调用所有终结器,可以使用以下技巧:
GC.Collect();
// Waiting till finilizer thread will call all finalizers
GC.WaitForPendingFinalizers();
当垃圾被收集时(无论是响应内存压力还是响应
GC.Collect()
),需要终结的对象被放入终结队列
除非调用GC.WaitForPendingFinalizers()
,否则在垃圾收集完成后很长时间内,终结器可能会继续在后台执行
顺便说一句,根本不能保证会调用终结器。从 Finalize方法可能无法运行到完成,或者可能无法在运行时运行 在下列特殊情况下:
- 另一个终结器无限期地阻塞(进入无限循环,尝试获取它永远无法获得的锁,依此类推)。因为 运行时尝试运行终结器以完成其他终结器 如果终结器无限期阻止,则可能不会调用
- 进程终止时,运行时没有机会进行清理。在本例中,运行时的第一个进程通知 终止是DLL_进程_分离通知
Collect
终结器线程在跟踪收集器找到垃圾后运行终结器,这与调用Collect
是异步的。(如果它真的发生了,正如另一个答案所指出的,它可能不会发生。)也就是说,在控件从Collect
返回之前,您不能依赖于执行的终结器线程
下面是一个简单的工作原理示意图:
- 当收集发生时,垃圾收集器跟踪线程跟踪根(已知为活动对象的对象,以及它们引用的每个对象等),以确定死对象李>
- 具有挂起终结器的“死”对象将移动到终结器队列中终结器队列是根。因此,那些“死”的物体实际上仍然活着
- 终结器线程(通常与GC跟踪线程不同)最终运行并清空终结器队列。然后,这些对象将真正失效,并在跟踪线程的下一个集合中收集。(当然,由于它们刚刚从第一个系列中幸存下来,它们可能是更高一代的。)
Collect
也会运行终结器,因为它不会。让我再重复一遍:垃圾收集器的跟踪部分不运行终结器,而收集
只运行收集机制的跟踪部分
如果要确保所有终结器都已运行,请在调用Collect
后调用适当命名的WaitForPendingFinalizers
。这将暂停当前线程,直到终结器线程开始清空队列。如果您想确保这些最终确定的对象回收了内存,那么您必须再次调用Collect
当然,不用说,你应该只做f