我已经开始审查项目中的一些代码并发现了类似的内容:
GC.Collect();
GC.WaitForPendingFinalizers();
这些线通常出现在被设想在提高效率的基础上破坏对象的方法上。我已经发表了这番话:
1,2和3是真的吗?你能给出一些支持你答案的参考吗?
虽然我几乎肯定我的言论,但我需要在论证中明确,以便向我的团队解释为什么这是一个问题。这就是我要求确认和参考的原因。
答案 0 :(得分:28)
简短的回答是:把它拿出来。该代码几乎永远不会提高性能或长期使用内存。
你的所有观点都是正确的。 (它可以生成死锁;这并不意味着它总是将。)调用GC.Collect()
将收集所有GC生成的内存。这样做有两件事。
它将不可收集的对象提升为下一代。也就是说,每次强制收集并且仍然引用某个对象时,该对象将被提升为后续生成。通常这种情况相对较少发生,但下面的代码会更频繁地强制执行:
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()
,则会在下一次机会时收集这两个项目。 将集合作为writte,o2
将在Gen1中结束 - 这意味着自动Gen0集合将不会释放该内存。
值得注意的还有一个更大的恐怖:在DEBUG模式下,GC的功能不同,并且不会回收任何仍在范围内的变量(即使它在以后的当前方法中没有使用)。因此,在DEBUG模式下,上面的代码在调用o1
时甚至不会收集GC.Collect
,因此o1
和o2
都会被提升。调试代码时,这可能会导致一些非常不稳定和意外的内存使用。 (诸如this之类的文章强调了这种行为。)
编辑:刚刚测试过这种行为,有些讽刺:如果你有类似的方法:
void CleanUp(Thing someObject)
{
someObject.TidyUp();
someObject = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
...然后它会明确地释放someObject的内存,即使在RELEASE模式下:它会将它推广到下一代GC。
答案 1 :(得分:8)
有一点可以让人很容易理解:GC运行会自动清理每次运行的许多对象(比如10000)。每次破坏后调用它会清除每次运行一个对象。
因为GC有很高的开销(需要停止和启动线程,需要活动扫描所有对象),所以批量调用是非常可取的。
此外,在每个对象之后清理可能会出现什么好?这怎么可能比批处理更有效?
答案 2 :(得分:6)
您的第3点在技术上是正确的,但只有在终结期间有人锁定时才会发生。
即使没有这种电话,锁定在终结者中也比你在这里更糟糕。
有几次调用GC.Collect()
确实有助于提高绩效。
到目前为止,我已经完成了2次,也许是我职业生涯中的3次。 (或者如果你包括我做过的那些,可能大约5到6次,测量结果,然后再把它拿出来 - 这是你应该总是测量后的事情)。
如果你在很短的时间内翻过几百或几千兆的内存,然后在很长一段时间内切换到不那么密集的内存使用,它可能是一个巨大的甚至是大量的显着收集的重要改进。那是在发生什么事吗?
在其他任何地方,他们最多会让它变慢并使用更多内存。
答案 3 :(得分:5)
请在此处查看我的其他答案:
<强> To GC.Collect or not? 强>
当你自己调用GC.Collect()时,可能会发生两件事情:你最终花费更多时间进行收集(因为除了手册GC.Collect之外,正常的背景收集仍会发生) ))并且你将继续留在内存更长(因为你强迫某些东西进入更高阶的生成而不需要去那里)。换句话说,自己使用GC.Collect()几乎总是一个坏主意。
关于您唯一想要自己调用GC.Collect()的时间,当您获得有关您的程序的特定信息时,垃圾收集器很难知道。规范示例是一个长期运行的程序,具有明显的忙碌和轻载周期。您可能希望在繁忙周期之前的轻负载期间结束时强制收集,以确保繁忙周期中的资源尽可能自由。但即便在这里,您可能会发现通过重新思考应用程序的构建方式(即计划任务是否能更好地运行?)来做得更好。)
答案 4 :(得分:2)
我只使用过一次:清理Crystal Report文档的服务器端缓存。请参阅Crystal Reports Exception: The maximum report processing jobs limit configured by your system administrator has been reached
中的回复WaitForPendingFinalizers对我特别有帮助,因为有时对象没有被正确清理。考虑到网页中报告的性能相对较慢 - 任何较小的GC延迟都可以忽略不计,内存管理的改进为我提供了一个整体更快乐的服务器。
答案 5 :(得分:2)
我们遇到了与@Grzenio类似的问题,但我们正在使用更大的二维数组,大约1000x1000到3000x3000,这是在web服务中。
添加更多内存并不总是正确的答案,您必须了解您的代码和用例。如果没有GC收集,我们需要16-32GB的内存(取决于客户的大小)。如果没有它,我们将需要32-64gb的内存,即便如此,也无法保证系统不会受到影响。 .NET垃圾收集器并不完美。
我们的web服务有一个内存缓存,大小为5-50万字符串(每个键/值对约80-140个字符,具体取决于配置),另外每个客户端请求我们将构造2个矩阵之一,布尔值之一,然后传递给另一个服务来完成工作。对于1000x1000&#34;矩阵&#34; (二维数组)这是〜25mb,每个请求。布尔值会说明我们需要哪些元素(基于我们的缓存)。每个缓存条目代表一个&#34; cell&#34;在&#34;矩阵&#34;。
当服务器具有&gt;时,缓存性能会急剧下降。分页导致80%的内存利用率。
我们发现,除非我们明确GC,否则.net垃圾收集器永远不会清理&#39;暂时变量,直到我们处于90-95%范围内,此时缓存性能急剧下降。
由于下游过程通常需要很长时间(3-900秒),因此GC收集的性能损失可以忽略不计(每次收集3-10秒)。我们在之后发起了此次收集我们已经将响应返回给客户端。
最终我们使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));
}
在重写使用.net 4.6之后,我们将垃圾收集分为两个步骤 - 一个简单的收集和一个压缩收集。
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)");
}
}
希望这可以帮助别人,因为我们花了很多时间研究这个。