JPC*_*PCF 31 .net c# garbage-collection
我已经开始检查项目中的一些代码,发现这样的事情:
GC.Collect();
GC.WaitForPendingFinalizers();
Run Code Online (Sandbox Code Playgroud)
这些线通常出现在被设想在提高效率的基础上破坏对象的方法上.我发表了这样的言论:
1,2和3都是真的吗?你能给出一些支持你答案的参考吗?
虽然我几乎可以肯定我的言论,但我需要明确我的论点,以便向我的团队解释为什么这是一个问题.这就是我要求确认和参考的原因.
Dan*_*zey 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();
}
Run Code Online (Sandbox Code Playgroud)没有a GC.Collect(),这两个项目将在下次机会时收集. 随着集合为writte,o2将最终在Gen1中 - 这意味着自动Gen0集合将不会释放该内存.
值得注意的还有一个更大的恐怖:在DEBUG模式下,GC的功能不同,并且不会回收任何仍在范围内的变量(即使它在当前方法中未被使用).所以在调试模式下,上面的代码甚至不会收集o1打电话时GC.Collect,所以两者o1并o2会得到提升.调试代码时,这可能会导致一些非常不稳定和意外的内存使用.(文章如这个亮点这种行为.)
编辑:刚刚测试过这种行为,有些讽刺:如果你有一个像这样的方法:
void CleanUp(Thing someObject)
{
someObject.TidyUp();
someObject = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
Run Code Online (Sandbox Code Playgroud)
...然后它会明确地释放someObject的内存,即使在RELEASE模式下:它会将它推广到下一代GC生成.
有一点可以让人很容易理解:GC运行会自动清理每次运行的许多对象(比如10000).每次破坏后调用它会清除每次运行一个对象.
由于GC具有高开销(需要停止和启动线程,需要扫描所有对象),因此批量调用是非常可取的.
此外,每个物体后清理还有什么好处?这怎么可能比批处理更有效?
您的第3点在技术上是正确的,但只有在终结期间有人锁定时才会发生.
即使没有这种呼叫,锁定终结器内部甚至比你在这里更糟糕.
有几次调用GC.Collect()确实有助于提高性能.
到目前为止,我已经完成了2次,也许是我职业生涯中的3次.(或者如果你包括那些我做过的那些,可能大约5到6次,测量结果,然后再把它拿出来 - 这是你应该经常做的事情).
如果您在短时间内通过数百或数千兆的内存进行搅拌,然后在很长一段时间内切换到不那么密集的内存使用,那么它可能是一个巨大的甚至是重要的改进.明确收集.那是在发生什么事吗?
在其他任何地方,他们最多都会让它变慢并使用更多内存.
在这里查看我的其他答案:
当您自己调用GC.Collect()时,可能会发生两件事:您最终花费更多的时间进行收集(因为除了手动的GC.Collect()之外,正常的后台收集仍会发生),并且您将继续使用更长的内存时间(因为您将某些事情强行带入了不需要去那里的高阶生成)。换句话说,自己使用GC.Collect()几乎总是一个坏主意。
大约是您唯一一次想要自己调用GC.Collect()的时间,是因为您拥有程序的特定信息,而这些信息对于垃圾收集器是很难知道的。典型的例子是一个长期运行的程序,具有明显的繁忙和轻负载周期。您可能想要在繁忙周期之前的轻负载周期即将结束时强制执行收集,以确保在繁忙周期中资源尽可能空闲。但是即使在这里,您也可以通过重新考虑应用程序的构建方式来发现自己做得更好(即,计划任务能否更好地工作?)。
我们遇到了与@Grzenio 类似的问题,但是我们正在处理更大的二维数组,大约为 1000x1000 到 3000x3000,这是在网络服务中。
添加更多内存并不总是正确的答案,您必须了解您的代码和用例。如果没有 GC 收集,我们需要 16-32gb 的内存(取决于客户的大小)。如果没有它,我们将需要 32-64gb 的内存,即使这样也不能保证系统不会受到影响。.NET 垃圾收集器并不完美。
我们的网络服务有一个内存缓存,大约有 5-5000 万个字符串(每个键/值对约 80-140 个字符,具体取决于配置),此外,对于每个客户端请求,我们将构造 2 个矩阵,一个是 double,一个是布尔值然后传递给另一个服务来完成工作。对于 1000x1000“矩阵”(二维数组),每个请求大约为 25mb 。布尔值会说明我们需要哪些元素(基于我们的缓存)。每个缓存条目代表“矩阵”中的一个“单元”。
当服务器由于分页而具有 > 80% 的内存利用率时,缓存性能会显着降低。
我们发现,除非我们明确地 GC,否则 .net 垃圾收集器永远不会“清理”临时变量,直到我们处于 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));
}
Run Code Online (Sandbox Code Playgroud)
在使用 .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)");
}
}
Run Code Online (Sandbox Code Playgroud)
希望这可以帮助其他人,因为我们花了很多时间研究这个。
| 归档时间: |
|
| 查看次数: |
26885 次 |
| 最近记录: |