C# – 垃圾收集

好的,所以我理解堆栈和堆(堆栈上的值,堆上的引用)。

当我声明一个类的新实例时,它会存在于堆上,并在堆栈的内存中引用此点。 我也知道C#是自己的垃圾收集(即它确定何时不再使用实例化的类并回收内存)。

我有两个问题:

  1. 我对垃圾收集的理解是否正确?
  2. 我可以自己做吗? 如果是这样,我自己这样做有什么好处,或者我应该离开它。

我问,因为我在For循环中有一个方法。 每次循环时,我都会创建一个新类的实例。 在我的脑海中,我想象所有这些类在堆里面,除了占用记忆之外什么都不做,我想尽可能快地摆脱它们以保持干净整洁!

我是正确地理解这个还是我错过了什么?

好的,所以我理解堆栈和堆(堆栈上的值,堆上的引用

我认为你不了解堆栈和堆。 如果值存在于堆栈中,那么整数数组在哪里? 整数是价值观。 你是否告诉我整数数组将其整数保留在堆栈中? 当你从一个方法中返回一个整数数组时,比如说,其中有一万个整数,你是否告诉我那些一万个整数被复制到堆栈中?

当它们存在于堆栈中时,它们存在于堆栈中,并且当它们存在于堆上时它们存在于堆上。 事物的类型 与其存储的生命周期有关的想法是无稽之谈。 短期存储位置在堆栈中; 长期存储的存储位置在堆上,并且与它们的类型无关。 一个长期存在的int必须在堆上,与一个类的长期实例相同。

当我声明一个类的新实例时,它会存在于堆上,并在堆栈的内存中引用此点。

为什么引用必须在堆栈上? 同样, 引用存储的生命周期与其类型无关 。 如果引用的存储是长期存储的,那么引用将在堆上进行。

我也知道C#是自己的垃圾收集(即它确定何时不再使用实例化的类并回收内存)。

C#语言没有这样做; CLR这样做了。

我对垃圾收集的理解是否正确?

你似乎相信很多关于堆栈和堆的谎言,所以赔率不好,不是。

我可以自己做吗?

不是在C#中,没有。

我问,因为我在For循环中有一个方法。 每次循环时,我都会创建一个新类的实例。 在我的脑海中,我想象所有这些类在堆里面,除了占用记忆之外什么都不做,我想尽可能快地摆脱它们以保持干净整洁!

垃圾收集的全部目的是让您免于担心整理。 这就是为什么它被称为“自动垃圾收集”。 它为你整理。

如果您担心自己的循环会产生收集压力 ,并且出于性能原因希望避免收集压力,那么我建议您采用汇集策略。 从明确的汇集策略开始是明智的; 那是:

while(whatever) { Frob f = FrobPool.FetchFromPool(); f.Blah(); FrobPool.ReturnToPool(f); } 

而不是尝试使用复活终结器进行自动池化。 除非你是终结语义专家,否则我一般会反对终结者和对象复活。

如果池中没有池,则池当然会分配一个新的Frob。 如果池中有一个,那么它将其移出并将其从池中移除直到它被放回。(如果你忘记将Frob放回池中,GC最终会到达它。)通过追求一个池策略,你导致GC最终将所有Frobs移动到第2代堆,而不是在第0代堆中创建大量的收集压力。 然后收集压力消失,因为没有分配新的Frobs。 如果其他东西正在产生收集压力,那么Frobs都安全地位于gen 2堆中,很少有人访问它们。

这当然与您描述的策略完全相反; 汇集策略的重点是让对象永远存在 。 如果您要使用它们,永远闲逛的物体是一件好事。

当然,在您通过分析知道由于收集压力导致性能问题之前,请不要进行这些更改! 在桌面CLR上很少出现这样的问题; 它在紧凑型CLR上更为常见。

更一般地说,如果你是那种让memory management员按照时间表清理你感到不舒服的人,那么C#就不适合你了。 考虑C而不是。

值存在于堆栈上,堆上的引用

这是一个实现细节 。 没有什么可以阻止.NET Framework在堆栈中存储它们。

我也知道C#是自己的垃圾收集

C#与此无关。 这是CLR提供的服务。 VB.NET,F#等都还有垃圾收集。

如果没有强根,CLR将从内存中删除一个对象。 例如,当您的类实例超出for循环范围时。 会有几个人躺着,但最终会通过垃圾收集或程序终止来收集它们。

我可以自己做吗? 如果是这样,我自己这样做有什么好处,还是我应该离开呢?

您可以使用GC.Collect强制收集。 你不应该这样做,因为这是一项昂贵的操作。 让一些物品占用内存的时间比绝对需要的时间长一些。 GC非常擅长自己的工作。 你也会强迫短命的物体升级到他们不能正常使用的世代。

首先, 关于价值类型真相的 Erics开创性post

其次,在垃圾收集方面,收集者对你的运行程序的了解远远超过你所知道的,除非你遇到内存泄漏的极不可能的情况,否则不要再猜测它。

所以对于你的第二个问题,不要试图“帮助”GC。

我会在CG上找到这个效果的post并更新这个答案。

我可以自己做吗? 如果是这样,我自己这样做有什么好处,或者我应该离开它。

是的,你可以使用GC.Collect 但你不应该 。 GC针对短期变量,方法变量和长寿命变量进行了优化,这些变量通常会在应用程序的生命周期内保持不变。

介于两者之间的变量并不常见,对GC来说并不是最佳选择。

通过强制GC.Collect,你更有可能导致范围中的变量被强制进入中间状态,这与你试图完成的相反。

另请参阅MSDN文章编写高性能托管应用程序:入门

GC是自我调整的,可根据应用程序内存要求进行自我调整。 在大多数情况下,以编程方式调用GC将阻碍调整。 通过调用GC.Collect“帮助”GC很可能无法提高应用程序性能

您对垃圾收集的理解已经足够了。 从本质上讲,未引用的实例被视为超出范围且不再需要。 确定了这一点后,收集器将在未来的某个时刻删除未引用的对象。

没有办法强制垃圾收集器只收集一个特定的实例。 你可以要求它做正常的“收集所有可能的”操作GC.Collect() ,但你不应该。 如果你把它留在自己的设备上,垃圾收集器是高效和有效的。

特别是它擅长收集寿命短的对象,就像那些作为临时对象创建的对象一样。 您不必担心在循环中创建对象的负载,除非它们具有阻止立即收集的长寿命。

请参阅有关堆栈和堆的相关问题 。

在您的特定场景中,同意,如果您在for循环中新建对象,那么您将获得次优性能。 对象是在循环中存储(或以其他方式使用),还是被丢弃? 如果是后者,你可以通过在循环外新建一个对象并重新使用它来优化它吗?

关于是否可以实现自己的GC,C#中没有明确的删除关键字,您必须将其留给CLR。 然而,您可以提供诸如何时收集或在收集期间忽略什么的提示,但是除非绝对必要,否则我会离开。

最好的祝福,

阅读Microsoft的以下文章,以获得有关C#中垃圾收集的一定程度的知识。 我相信它会帮助任何需要有关此事的信息的人。

.NET Framework中的内存管理和垃圾收集

如果您在编写C#时对代码中某些区域的性能感兴趣,则可以编写不安全的代码 。 您将获得性能加分,并且在您的固定块中,垃圾收集器很可能不会发生。

垃圾收集基本上是参考跟踪。 我想不出有什么理由要你改变它。 如果您发现内存未被释放,您是否遇到某种问题? 或许您正在寻找处置模式

编辑:用“参考跟踪”替换“引用计数”,不要与对象引用/解除引用上的增量/减量计数器混淆(例如从Python)。 我认为将对象图生成称为“计数”是很常见的,就像在这个答案中一样: 为什么没有引用计数+ C#中的垃圾收集? 但我不会拿起(Eric)Lippert的手套:)