C# 为什么可以';垃圾收集器不知道相互引用的对象何时真的是孤立的吗

C# 为什么可以';垃圾收集器不知道相互引用的对象何时真的是孤立的吗,c#,garbage-collection,C#,Garbage Collection,我知道,在Java或C等托管语言中,有一种称为垃圾收集器的东西,它每隔一段时间检查是否有任何对象实例不再被引用,因此完全孤立,然后清除内存。但是,如果两个对象没有被程序中的任何变量引用,而是相互引用(如事件订阅),垃圾收集器将看到此引用,而不会从内存中清除这些对象 为什么会这样?为什么垃圾收集器不能判断出运行程序的任何活动部分都不能引用这两个对象并将其丢弃。您的假设是错误的。如果GC“看到”(见下文)2个或更多对象的循环引用(在“标记”阶段),但未被任何其他对象或永久GC句柄(强引用)引用,则将

我知道,在Java或C等托管语言中,有一种称为垃圾收集器的东西,它每隔一段时间检查是否有任何对象实例不再被引用,因此完全孤立,然后清除内存。但是,如果两个对象没有被程序中的任何变量引用,而是相互引用(如事件订阅),垃圾收集器将看到此引用,而不会从内存中清除这些对象


为什么会这样?为什么垃圾收集器不能判断出运行程序的任何活动部分都不能引用这两个对象并将其丢弃。

您的假设是错误的。如果GC“看到”(见下文)2个或更多对象的循环引用(在“标记”阶段),但未被任何其他对象或永久GC句柄(强引用)引用,则将收集这些对象(在“扫描”阶段)

在和中可以找到对CLR垃圾收集器的深入了解


注意:事实上,GC在标记阶段甚至不会“看到”这些类型的对象,因为它们是不可访问的,因此在扫描过程中会被收集。

大多数GC不再使用引用计数。它们通常(在Java和.NET中都是这样)使用对象根集的reach功能工作。对象的根集合是全局和堆栈引用实例。任何可以直接或间接从该集合访问的内容都是活动的。内存的其余部分无法访问,因此容易被收集

我想补充一点,围绕事件订阅的问题通常围绕订阅者和发布者具有非常不同的生命周期这一事实


将自己连接到Windows窗体中的App.Idle事件,您的对象将在应用程序的剩余生命周期内保持活动状态。为什么?该静态应用程序将保存对您注册的观察者的引用(尽管是通过代理间接进行的)。即使您可能已经处理了您的观察者,它仍然连接到App.Idle。你可以构造许多这样的例子。

这里的其他答案肯定是正确的。NET根据对象的可达性进行垃圾收集


我想补充的是:如果你想要更深入的信息,我可以推荐你阅读(安德鲁·亨特的《简单对话》一文)。

这是传统方法的一个主要缺点。描述此行为的垃圾收集器的属性是不完整的收集器。其他收集器基本上属于跟踪垃圾收集器的范畴,包括传统的标记清除、半空间/压缩和世代混合,它们没有这些缺点(但也面临其他一些缺点)

我所知道的所有JVM和CLI实现都使用完整的收集器,这意味着它们不会遇到您在这里询问的特定问题。据我所知,其中只有一个提供了引用计数采集器(众多采集器中的一个)

另一件值得注意的有趣事情是,在引用计数垃圾收集中有一些完整性问题的解决方案,这些解决方案演示了一些很难从跟踪收集器中获得的有趣的性能属性。不幸的是,性能最高的引用计数垃圾收集算法和大多数完整性修改都依赖于编译器的帮助,因此将它们带到C++的
shared_ptr
是很困难的/不会发生的。相反,我们有
weak_ptr
和(对于次优链接很抱歉-显然文档让我无法理解)简单地避免问题。这(另一个普通的链接)我们已经看到了这种方法,希望防止内存问题的额外工作少于维护不使用
共享\u ptr
等的代码所需的工作量


链接平庸是因为我的大部分参考资料分散在上学期的内存管理课程的笔记中。

事实上,这是.NET GC中处理的引用计数环境的问题之一。另一个传统的Lisp功能正在进入主流环境。实际上,我认为将引用计数称为垃圾收集的一种形式是错误的。它们是两种不同形式的内存管理。引用计数(RC)是垃圾收集(GC)的形式。简单的RC算法不能处理循环引用,但有Bobrow的技术,弱指针算法和部分标记扫描算法可以。好吧,这使事情变得容易多了。因此,我的程序有一个由节点组成的树结构,每个节点都通过树视图中的自定义树节点显示。如果没有引用节点或树节点,那么我就不必担心在它们之间分离事件或引用。