[只是想进一步补充最终确定过程的内部]
因此,您创建了一个对象,当收集到该对象时,Finalize
应该调用该对象的方法。但除了这个非常简单的假设之外,还有更多的事情要做。
简短的概念::
没有实现Finalize
方法的对象,内存被立即回收,当然,除非它们不再被
应用程序代码访问
实现方法的对象,Finalize
概念/实现Application Roots
,在它们可以被回收之前出现。Finalization Queue
Freacheable Queue
如果应用程序代码无法访问任何对象,则将其视为垃圾
假设:: 类/对象 A、B、D、G、H 不实现Finalize
方法,而 C、E、F、I、J 实现Finalize
方法。
当应用程序创建一个新对象时,new 运算符从堆中分配内存。如果对象的类型包含Finalize
方法,则指向该对象的指针被放置在 finalization queue中。
因此指向对象 C、E、F、I、J 的指针被添加到终结队列中。终结队列
是由垃圾收集器控制
的内部数据结构。队列中的每个条目都指向一个对象,该对象应该在对象的内存被回收之前调用其方法。下图显示了一个包含多个对象的堆。其中一些对象可以从应用程序的根目录访问Finalize
,有些不是。创建对象 C、E、F、I 和 J 时,.Net 框架会检测到这些对象具有Finalize
方法,并将指向这些对象的指针添加到终结队列中。
当发生 GC(第一次收集)时,对象 B、E、G、H、I 和 J 被确定为垃圾。因为 A、C、D、F 仍然可以通过上方黄色框中的箭头所示的应用程序代码访问。
垃圾收集器扫描终结队列寻找指向这些对象的指针。当找到一个指针时,该指针将从终结队列中移除并附加到可访问队列(“F-reachable”)。freachable 队列是由垃圾收集器控制的另一个内部数据结构
。freachable 队列中的每个指针都标识了一个准备好调用其方法的对象。Finalize
在集合(第一个集合)之后,托管堆看起来类似于下图。解释如下::
1.)对象 B、G 和 H 占用的内存已被立即回收,因为这些对象没有需要调用的 finalize 方法。
2.) 但是,对象 E、I 和 J 占用的内存无法回收,因为它们的Finalize
方法还没有被调用。
调用 Finalize 方法是由freacheable 队列完成的。
3.) A,C,D,F 仍然可以通过上面黄色框箭头所示的应用程序代码访问,因此在任何情况下都不会收集它们
有一个专门用于调用 Finalize 方法的特殊运行时线程。当 freachable 队列为空时(通常是这种情况),该线程休眠。但是当条目出现时,该线程唤醒,从队列中删除每个条目,并调用每个对象的 Finalize 方法。垃圾收集器压缩可回收内存,特殊运行时线程清空易碎队列,执行每个对象的Finalize
方法。所以最后是你的 Finalize 方法被执行的时候
下次调用垃圾收集器(第二次收集)时,它会看到最终对象是真正的垃圾,因为应用程序的根不再指向它,并且freachable 队列不再指向它(它也是 EMPTY),因此对象(E、I、J)的内存只是从堆中回收的。参见下图并将其与上图进行比较
这里要理解的重要一点是,需要两次 GC 来回收需要终结的对象使用的内存。实际上,甚至需要两个以上的集合,因为这些对象可能会升级到老一代
注意: freachable队列被认为是根,就像全局和静态变量是根一样。因此,如果一个对象在 freachable 队列上,则该对象是可访问的并且不是垃圾。
最后一点,请记住调试应用程序是一回事,垃圾收集是另一回事,并且工作方式不同。到目前为止,您无法仅通过调试应用程序来感受垃圾收集,如果您想进一步研究内存,请从这里开始。