问题描述
我开始在一个项目中查看一些代码,发现是这样的:
I've started to review some code in a project and found something like this:
GC.Collect();
GC.WaitForPendingFinalizers();
这些行通常出现在旨在提高效率的基本原理下破坏对象的方法上.我做了这样的评论:
Those lines usually appear on methods that are conceived to destruct the object under the rationale of increase efficiency. I've made this remarks:
- 在销毁每个对象时显式调用垃圾回收会降低性能,因为这样做没有考虑到 CLR 性能是否绝对必要.
- 仅当其他对象正在完成时,按该顺序调用这些指令会导致每个对象被销毁.因此,一个可以独立销毁的对象必须等待另一个对象的销毁,而没有真正的必要性.
- 它会产生死锁(参见:this问题)
1、2 和 3 是真的吗?你能提供一些参考来支持你的答案吗?
Are 1, 2 and 3 true? Can you give some reference supporting your answers?
虽然我几乎可以肯定我的言论,但我需要在我的论点中明确说明,以便向我的团队解释为什么这是一个问题.这就是我要求确认和参考的原因.
Although I'm almost sure about my remarks, I need to be clear in my arguments in order to explain to my team why is this a problem. That's the reason I'm asking for confirmation and reference.
推荐答案
简短的回答是:拿出来.该代码几乎永远不会提高性能或长期内存使用.
The short answer is: take it out. That code will almost never improve performance, or long-term memory use.
您的所有观点都是正确的.(它可以产生死锁;这并不意味着它总是会.)调用 GC.Collect()
将收集所有 GC 的内存几代人.这有两件事.
All your points are true. (It can generate a deadlock; that does not mean it always will.) Calling GC.Collect()
will collect the memory of all GC generations. This does two things.
- 它每次跨所有代收集 - 而不是默认情况下 GC 将执行的操作,即仅在代满时收集.典型的使用将看到 Gen0 的收集频率(大约)是 Gen1 的十倍,而 Gen1 的收集频率(大约)是 Gen2 的十倍.这段代码每次都会收集所有的世代.Gen0 收集通常低于 100 毫秒;Gen2 可以更长.
它将不可收集的物品推广到下一代.也就是说,每次你强制一个集合并且你仍然有对某个对象的引用时,那个对象将被提升到下一代.通常这种情况很少发生,但像下面这样的代码会更频繁地发生这种情况:
- It collects across all generations every time - instead of what the GC will do by default, which is to only collect a generation when it is full. Typical use will see Gen0 collecting (roughly) ten times as often than Gen1, which in turn collects (roughly) ten times as often as Gen2. This code will collect all generations every time. Gen0 collection is typically sub-100ms; Gen2 can be much longer.
It promotes non-collectable objects to the next generation. That is, every time you force a collection and you still have a reference to some object, that object will be promoted to the subsequent generation. Typically this will happen relatively rarely, but code such as the below will force this far more often:
void SomeMethod()
{
object o1 = new Object();
object o2 = new Object();
o1.ToString();
GC.Collect(); // this forces o2 into Gen1, because it's still referenced
o2.ToString();
}
如果没有 GC.Collect()
,这两个项目都将在下次有机会时被收集. 集合被写入后,o2
将在 Gen1 中结束 - 这意味着自动 Gen0 集合不会释放该内存.
Without a GC.Collect()
, both of these items will be collected at the next opportunity. With the collection as writte, o2
will end up in Gen1 - which means an automated Gen0 collection won't release that memory.
还值得注意的是更大的恐怖:在 DEBUG 模式下,GC 的功能不同,并且不会回收仍在作用域内的任何变量(即使在当前方法中稍后未使用它).所以在 DEBUG 模式下,上面的代码在调用 GC.Collect
时甚至不会收集 o1
,所以 o1
和 o2
将被提升.在调试代码时,这可能会导致一些非常不稳定和意外的内存使用.(this 等文章强调了这种行为.)
It's also worth noting an even bigger horror: in DEBUG mode, the GC functions differently and won't reclaim any variable that is still in scope (even if it's not used later in the current method). So in DEBUG mode, the code above wouldn't even collect o1
when calling GC.Collect
, and so both o1
and o2
will be promoted. This could lead to some very erratic and unexpected memory usage when debugging code. (Articles such as this highlight this behaviour.)
刚刚测试了这种行为,有点讽刺的是:如果你有这样的方法:
Having just tested this behaviour, some real irony: if you have a method something like this:
void CleanUp(Thing someObject)
{
someObject.TidyUp();
someObject = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
...然后它不会显式释放 someObject 的内存,即使在 RELEASE 模式下:它会将其提升到下一代 GC.
... then it will explicitly NOT release the memory of someObject, even in RELEASE mode: it'll promote it into the next GC generation.
这篇关于使用 GC.Collect() 是正确的;GC.WaitForPendingFinalizers();?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!