C#中的垃圾回收和作用域是如何工作的?

18
我正在学习C#,之前学过Python,希望了解C#垃圾收集器的工作原理。我发现,一旦我明白了Python背后的原理,就会对Python有更深的理解,也希望避免在学习Python时犯的初学者错误。
我找不到任何清晰的解释,说明什么时候会进行垃圾回收,因此还有一些问题:
  1. 当一个对象的最后一个引用超出范围时,该对象会发生什么?它是否被垃圾回收,或者在返回定义它的范围时仍然存在?
  2. 何时减少引用数量?这让我想知道它是否使用引用计数或其他技术...
回答这些问题,甚至提供清晰简明的概述,将获得饼干(或赞),如果你的答案还能比较一下Python的做法,那就更棒了。我不关心哪种方法更好,只是想了解细节。同时,在programmers.stackexchange上回答我的原帖将不胜感激...
4个回答

25

dotnet的GC引擎是一种标记-清除引擎,而不是像Python中常见的引用计数引擎。该系统不维护变量引用计数,而是在需要回收内存时运行“清理程序”,标记所有当前可达指针,并移除所有不可达的指针(因此超出作用域)。

您可以在这里了解更多有关其工作原理的信息:
http://msdn.microsoft.com/en-us/library/ee787088.aspx

该系统通过从特定的“根”位置开始,如全局对象和堆栈上的对象,找到“可达”对象,并跟踪由这些对象引用的所有对象,以及由它们引用的所有对象等,直到构建完整的树状结构。这比听起来要快。


6

在对一个对象的最后一次引用消失后的某个不确定时间点,该对象将被收集。

你第一个问题的第二部分没有意义。
如果你可以重新进入定义对象的作用域(例如lambda表达式),那么显然仍然存在一个引用。

垃圾回收器根本不使用引用计数。
相反,它使用了一种“标记-清除”算法,更多细节可参考此处链接


好的,那么举个例子:我正在运行一个循环,有没有一种方法可以在第一次进入该循环时实例化一个变量,然后在下一次循环中该变量仍然保留其旧值,直到您更改它,而不是未实例化? - theheadofabroom
@Bigg:只需将变量移到循环外即可。 - SLaks
我不是在寻求解决方法,而是想了解行为应该是什么。我希望答案是否定的,这只是一个粗略的例子,旨在说明我的思路。 - theheadofabroom
1
不移动变量。该变量的作用域限定在循环的内部块中;在该迭代之后,它就消失了(除非在其他地方保留,例如 lambda 函数)。 - SLaks

5
垃圾回收不是由引用超出范围触发的。垃圾回收通常在为新对象分配存储空间时触发,特别是当零代预算耗尽时。也就是说,对象有资格进行垃圾回收和实际回收之间可能会有很大的延迟。正如其他人已经指出的那样,CLR不使用引用计数,而是采用标记和清除方法。
关于垃圾回收的所有细节信息的一个好的信息来源是Jeffrey Ricther的书CLR via C#。这本书详细介绍了堆如何分区以及垃圾回收的工作原理。如果您对.NET实现细节感兴趣,强烈推荐。

3
当一个对象不再有强引用时,不会立即发生任何事情。每个对象都被分配一个代号,0、1或2。最初,所有对象都放置在0代中。使用标记-清除算法,垃圾回收器将定期检查一代。如果实例仍然具有任何强引用,则将其提升到更高的一代,最高为2(通常较少检查)。这背后的理论是大多数对象通常寿命短暂,因此如果一个对象已经存活了足够长的时间以使其进入第1代,则不需要经常进行检查。
.NET GC中没有引用计数。相反,它使用标记和清除。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接