C# 使用 GC.Collect() 是正确的;GC.WaitForPendingFinalizers();?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/12265598/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Is correct to use GC.Collect(); GC.WaitForPendingFinalizers();?
提问by JPCF
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:
这些线条通常出现在旨在提高效率的基础上破坏对象的方法上。我做了这样的评论:
- To call garbage collection explicitly on the destruction of every object decreases performance because doing so does not take into account if it is absolutely necessary for CLR performance.
- Calling those instructions in that order causes every object to be destroyed only if other objects are being finalized. Therefore, an object that could be destroyed independently has to wait for another object's destruction without a real necessity.
- It can generate a deadlock (see: this question)
- 在销毁每个对象时显式调用垃圾回收会降低性能,因为这样做不会考虑 CLR 性能是否绝对必要。
- 仅当其他对象正在被终结时,按该顺序调用这些指令会导致每个对象被销毁。因此,一个可以独立销毁的对象必须在没有真正必要的情况下等待另一个对象的销毁。
- 它可以产生一个死锁(见:这个问题)
Are 1, 2 and 3 true? Can you give some reference supporting your answers?
1、2和3是真的吗?你能提供一些支持你的答案的参考吗?
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.
虽然我几乎可以肯定我的评论,但我需要在我的论点中明确,以便向我的团队解释为什么这是一个问题。这就是我要求确认和参考的原因。
采纳答案by Dan Puzey
The short answer is: take it out. That code will almost neverimprove performance, or long-term memory use.
简短的回答是:把它拿出来。该代码几乎永远不会提高性能或长期内存使用。
All your points are true. (It cangenerate 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.Collect()将收集所有 GC 代的内存。这有两件事。
- 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 将执行的操作,即仅在代已满时收集代。在典型的使用中,Gen0 收集(大约)是 Gen1 的十倍,而 Gen1 收集(大约)是 Gen2 的十倍。此代码每次都会收集所有代。Gen0 收集通常低于 100 毫秒;Gen2 可以更长。
它将不可收集的对象提升到下一代。也就是说,每次你强制一个集合并且你仍然拥有对某个对象的引用时,该对象将被提升到下一代。通常,这种情况很少发生,但如下所示的代码会更频繁地强制执行此操作:
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(); }
Without a GC.Collect(), both of these items will be collected at the next opportunity. Withthe collection as writte, o2will end up in Gen1 - which means an automated Gen0 collection won'trelease that memory.
如果没有GC.Collect(),这两个项目将在下一次机会被收集。 与该集合作为被软件写,o2这意味着一个自动GEN0收集-将在第一代最终将不释放存储器。
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 o1when calling GC.Collect, and so both o1and o2will be promoted. This could lead to some very erratic and unexpected memory usage when debugging code. (Articles such as thishighlight this behaviour.)
还值得注意的是一个更大的恐怖:在 DEBUG 模式下,GC 的功能不同,并且不会回收任何仍在作用域内的变量(即使它在当前方法的后面没有使用)。所以在 DEBUG 模式下,上面的代码o1在调用 时甚至不会收集GC.Collect,所以o1和o2都会被提升。在调试代码时,这可能会导致一些非常不稳定和意外的内存使用。(文章如此强调这一行为)。
EDIT: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();
}
... then it will explicitly NOT release the memory of someObject, even in RELEASE mode: it'll promote it into the next GC generation.
...然后它不会明确释放 someObject 的内存,即使在 RELEASE 模式下:它会将它提升到下一代 GC 中。
回答by usr
There is a point one can make that is very easy to understand: Having GC run automatically cleans up many objects per run (say, 10000). Calling it after every destruction cleans up about one object per run.
有一点很容易理解:每次运行 GC 会自动清理许多对象(比如 10000)。在每次销毁后调用它每次运行会清理大约一个对象。
Because GC has high overhead (needs to stop and start threads, needs to scan all objects alive) batching calls is highly preferable.
因为 GC 的开销很高(需要停止和启动线程,需要扫描所有活着的对象)批处理调用是非常可取的。
Also, what goodcould come out of cleaning up after every object? How could this be more efficient than batching?
此外,在每件物品之后进行清理会有什么好处?这如何比批处理更有效?
回答by Joel Coehoorn
See my other answer here:
在这里查看我的另一个答案:
two things can happen when you call GC.Collect() yourself: you end up spending moretime doing collections (because the normal background collections will still happen in addition to your manual GC.Collect()) and you'll hang on to the memory longer(because you forced some things into a higher order generation that didn't need to go there). In other words, using GC.Collect() yourself is almost always a bad idea.
当您自己调用 GC.Collect() 时,可能会发生两件事:您最终会花费更多时间进行收集(因为除了手动 GC.Collect() 之外,正常的后台收集仍然会发生),并且您将继续使用内存更长(因为你强迫一些东西进入不需要去那里的更高阶的一代)。换句话说,自己使用 GC.Collect() 几乎总是一个坏主意。
About the only time you ever want to call GC.Collect() yourself is when you have specific information about your program that is hard for the Garbage Collector to know. The canonical example is a long-running program with distinct busy and light load cycles. You may want to force a collection near the end of a period of light load, ahead of a busy cycle, to make sure resources are as free as possible for the busy cycle. But even here, you might find you do better by re-thinking how your app is built (ie, would a scheduled task work better?).
大约唯一一次您想要自己调用 GC.Collect() 是当您有关于垃圾收集器很难知道的程序的特定信息时。典型的例子是一个长时间运行的程序,具有不同的繁忙和轻负载周期。您可能希望在繁忙周期之前的轻负载期结束时强制收集,以确保资源在繁忙周期中尽可能空闲。但即使在这里,您可能会发现通过重新思考您的应用程序是如何构建的(即,计划任务会更好地工作吗?),您会做得更好。
回答by Jon Hanna
Your point number 3 is technically correct, but can only happen if someone locks during a finaliser.
您的第 3 点在技术上是正确的,但只有在终结赛期间有人锁定时才会发生。
Even without this sort of call, locking inside a finaliser is even worse than what you have here.
即使没有这种调用,锁定在终结器中也比这里更糟糕。
There are a handful of times when calling GC.Collect()really does help performance.
有几次调用GC.Collect()确实有助于提高性能。
So far I've done so 2, maybe 3 times in my career. (Or maybe about 5 or 6 times if you include those where I did it, measured the results, and then took it out again - and this is something you should alwaysmeasure after doing).
到目前为止,在我的职业生涯中,我已经这样做了 2 次,也许 3 次。(或者,如果包括我所做的那些,测量结果,然后再次取出,则可能大约 5 或 6 次 - 这是您在完成后应始终测量的内容)。
In cases where you're churning through hundreds or thousands of megs of memory in a short period of time, and then switching over to much less intensive use of memory for a long period of time, it can be a massive or even vital improvement to explicitly collect. Is that what's happening here?
如果您在短时间内处理数百或数千兆内存,然后在很长一段时间内切换到低强度的内存使用,这可能是一个巨大的甚至是至关重要的改进明确收集。这就是这里发生的事情吗?
Anywhere else, they're at best going to make it slower and use more memory.
在其他任何地方,它们充其量只会让它变慢并使用更多内存。
回答by gojimmypi
I've used this just once: to clean up server-side cache of Crystal Report documents. See my response in Crystal Reports Exception: The maximum report processing jobs limit configured by your system administrator has been reached
我只用过一次:清理 Crystal Report 文档的服务器端缓存。请参阅我在Crystal Reports Exception 中的回复:已达到系统管理员配置的最大报表处理作业限制
The WaitForPendingFinalizers was particularly helpful for me, as sometimes the objects were not being cleaned up properly. Considering the relatively slow performance of the report in a web page - any minor GC delay was negligible, and the improvement in memory management gave an overall happier server for me.
WaitForPendingFinalizers 对我特别有帮助,因为有时对象没有被正确清理。考虑到网页中报告的性能相对较慢 - 任何轻微的 GC 延迟都可以忽略不计,内存管理的改进为我提供了一个整体更快乐的服务器。
回答by Justin
We have run into similar problems to @Grzenio however we are working with much larger 2-dimensional arrays, in the order of 1000x1000 to 3000x3000, this is in a webservice.
我们遇到了与@Grzenio 类似的问题,但是我们正在处理更大的二维数组,大约为 1000x1000 到 3000x3000,这是在 Web 服务中。
Adding more memory isn't always the right answer, you have to understand your code and the use case. Without GC collecting we require 16-32gb of memory (depending on customer size). Without it we would require 32-64gb of memory and even then there are no guarantees the system won't suffer. The .NET garbage collector is not perfect.
添加更多内存并不总是正确的答案,您必须了解您的代码和用例。如果没有 GC 收集,我们需要 16-32gb 的内存(取决于客户的大小)。如果没有它,我们将需要 32-64gb 的内存,即使这样也不能保证系统不会受到影响。.NET 垃圾收集器并不完美。
Our webservice has an in-memory cache in the order of 5-50 million string (~80-140 characters per key/value pair depending on configuration), in addition with each client request we would construct 2 matrices one of double, one of boolean which were then passed to another service to do the work. For a 1000x1000 "matrix" (2-dimensional array) this is ~25mb, per request. The boolean would say which elements we need (based on our cache). Each cache entry represents one "cell" in the "matrix".
我们的网络服务有一个大约 5-5000 万个字符串的内存缓存(每个键/值对约 80-140 个字符,具体取决于配置),此外,对于每个客户端请求,我们将构造 2 个矩阵,一个是 double,一个是布尔值然后传递给另一个服务来完成工作。对于 1000x1000“矩阵”(二维数组),每个请求大约为 25mb 。布尔值会说明我们需要哪些元素(基于我们的缓存)。每个缓存条目代表“矩阵”中的一个“单元”。
The cache performance dramatically degrades when the server has > 80% memory utilization due to paging.
当服务器由于分页而具有 > 80% 的内存利用率时,缓存性能会显着降低。
What we found is that unless we explicitly GC the .net garbage collector would never 'cleanup' the transitory variables until we were in the 90-95% range by which point the cache performance had drastically degraded.
我们发现,除非我们明确地 GC,否则 .net 垃圾收集器永远不会“清理”临时变量,直到我们处于 90-95% 的范围内,此时缓存性能已经急剧下降。
Since the down-stream process often took a long duration (3-900 seconds) the performance hit of a GC collection was neglible (3-10 seconds per collect). We initiated this collect afterwe had already returned the response to the client.
由于下游过程通常需要很长时间(3-900 秒),GC 收集的性能影响可以忽略不计(每次收集 3-10 秒)。在我们已经将响应返回给客户端之后,我们启动了这个收集。
Ultimately we made the GC parameters configurable, also with .net 4.6 there are further options. Here is the .net 4.5 code we used.
最终我们使 GC 参数可配置,在 .net 4.6 中还有更多选项。这是我们使用的 .net 4.5 代码。
if (sinceLastGC.Minutes > Service.g_GCMinutes)
{
Service.g_LastGCTime = DateTime.Now;
var sw = Stopwatch.StartNew();
long memBefore = System.GC.GetTotalMemory(false);
context.Response.Flush();
context.ApplicationInstance.CompleteRequest();
System.GC.Collect( Service.g_GCGeneration, Service.g_GCForced ? System.GCCollectionMode.Forced : System.GCCollectionMode.Optimized);
System.GC.WaitForPendingFinalizers();
long memAfter = System.GC.GetTotalMemory(true);
var elapsed = sw.ElapsedMilliseconds;
Log.Info(string.Format("GC starts with {0} bytes, ends with {1} bytes, GC time {2} (ms)", memBefore, memAfter, elapsed));
}
After rewriting for use with .net 4.6 we split the garbage colleciton into 2 steps - a simple collect and a compacting collect.
在重写以用于 .net 4.6 之后,我们将垃圾收集分为 2 个步骤 - 一个简单的收集和一个压缩收集。
public static RunGC(GCParameters param = null)
{
lock (GCLock)
{
var theParams = param ?? GCParams;
var sw = Stopwatch.StartNew();
var timestamp = DateTime.Now;
long memBefore = GC.GetTotalMemory(false);
GC.Collect(theParams.Generation, theParams.Mode, theParams.Blocking, theParams.Compacting);
GC.WaitForPendingFinalizers();
//GC.Collect(); // may need to collect dead objects created by the finalizers
var elapsed = sw.ElapsedMilliseconds;
long memAfter = GC.GetTotalMemory(true);
Log.Info($"GC starts with {memBefore} bytes, ends with {memAfter} bytes, GC time {elapsed} (ms)");
}
}
// https://msdn.microsoft.com/en-us/library/system.runtime.gcsettings.largeobjectheapcompactionmode.aspx
public static RunCompactingGC()
{
lock (CompactingGCLock)
{
var sw = Stopwatch.StartNew();
var timestamp = DateTime.Now;
long memBefore = GC.GetTotalMemory(false);
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect();
var elapsed = sw.ElapsedMilliseconds;
long memAfter = GC.GetTotalMemory(true);
Log.Info($"Compacting GC starts with {memBefore} bytes, ends with {memAfter} bytes, GC time {elapsed} (ms)");
}
}
Hope this helps someone else as we spent a lot of time researching this.
希望这可以帮助其他人,因为我们花了很多时间研究这个。

