抑制C#垃圾回收

14
我的应用程序分配了大量的内存(数以百万计的小对象总共几个GB),并长时间占用它。

  1. .NET 是否会浪费时间检查所有这些数据来进行垃圾回收?
  2. Gen 2 GC (检查所有对象的GC)发生频率有多高?
  3. 是否有任何方法可以减少其频率或暂时禁止其发生?
  4. 我知道什么时候可以释放大量内存,有没有办法进行优化?我目前正在调用 GC.Collect(); GC.WaitForPendingFinalizers();

更新: 性能计数器“%GC时间”显示平均值为10.6%。


那行代码大约每3分钟才会被执行一次,所以它可能不会产生太大的影响。 - Fantius
6个回答

25
除非你能确认垃圾回收器正在明显地影响应用程序的性能,否则不应该采取措施削弱运行时环境的功能。
从你的问题来看,你还没有确认GC是否是一个问题。我严重怀疑它是否是。
只优化需要优化的东西。

如果您的应用程序处理大量对象,那么您将会有更高的垃圾收集时间是可以预期的。如果您在 C++ 中编写了相同的代码,那么您几乎会花费相同的时间来分配、清零和释放内存块,甚至根据您的内存管理器效率而定,可能需要更多的时间。 - jrista
1
那么,考虑到我正在分配数百万个完全相同的类,然后一次性释放其中的大部分,是否有比典型的新建和丢弃更好的模式可供遵循? - Fantius
2
你可以改变应用程序的流程,使得对象要么非常快地被销毁(在第0代垃圾回收中),要么在整个应用程序生命周期内都存在,因此不会被回收。 - No More Hacks

11
你可以使用静态方法停止垃圾回收器对你的任何对象进行终结:
GC.SuppressFinalize(*your object*)

更多信息请参见:链接文字


1
那会引起资源泄漏,而且它并没有解决问题。 - H H
是的,这会导致资源泄漏,但如果您知道finalize的实际工作原理,它确实在某种程度上解决了问题。 - Joshua
我回答了最初的问题 - 但现在已经改变了。最初他问如何停止GC检查他的大对象。是否这是一个好主意是另一回事。 - Ken Pespisa
12
阅读有关该函数的说明,它似乎并没有阻止垃圾回收(GC)检查对象是否准备好进行回收。相反,它似乎抑制了在对象被回收时调用Finalize方法。两者非常不同。 - Fantius

9

看一下 System.Runtime.GCSettings.LatencyMode 属性。

在 app.config 中将 GCServer 属性设置为 true 也有助于减少 GC 的发生(在我的情况下启用后,GC 减少了 10 倍)。


我将其设置为LowLatency,但我没有观察到任何改进。 - Fantius
GCLatencyMode.SustainedLowLatency对于我来说在改善快速近实时信号处理循环的响应能力方面帮助很大,谢谢。 - metao

7
您可以使用性能监视器来测量这个。打开perfmon并添加与.NET CLR Memory相关的性能计数器。这些计数器是进程特定的,您可以使用它们跟踪各代的收集次数和大小,更为具体的是"% Time in GC"。这是该计数器的解释文本:
“% Time in GC是自上次GC周期以来花费在执行垃圾回收(GC)的经过时间的百分比。此计数器通常是Garbage Collector代表应用程序收集和压缩内存所做工作的指标。此计数器仅在每次GC结束时更新,并且计数器值反映了最后观察到的值;它不是平均值。”
如果您在运行程序时观察这些计数器,就可以回答关于GC频率和成本的问题,这均受内存决策的影响。 这里有一个有关各种GC性能计数器的良好讨论。似乎10%是勉强可以接受的。

3

只有当垃圾回收器需要一些gen2内存(因为gen1已满)时,它才会发生(通常)。你是在推测吗?还是你确实遇到了垃圾回收占用大量执行时间的问题?如果没有问题,我建议您暂时不要担心它 - 但请使用性能监视器密切关注。


1
我想问一下,我的理解是否有误,GC 是检查“指针”是否在使用中(或者使用频率),并且完全不关心每个单独对象的大小? - DevinB
1
据我所知,是的。另一方面,如果您有较少的大对象,则它们在标记/清除时所需的时间将比数百万个小对象少。但我建议尽量避免让这影响您的设计。 - Jon Skeet
除非你需要做出50/50的决定,否则它不应该影响你的设计。在这种情况下,你应该选择更容易优化的选项。假设可读性和其他因素相等。在我看来,拥有更多的信息总是更好的。 - DevinB
5
还有一点,请别忘了真正的大对象使用(恰如其分地)命名为 Large Object Heap 的堆,其垃圾回收策略与常规分代堆不同。 - Eric Lippert
哦,是的,我完全忘记了那一部分 :) 然而请记住,这是针对非常大的单个对象 - 而不是唯一引用大量对象的一个对象等。在我的经验中,只有数组和字符串最终出现在大对象堆上 - 这些是需要注意的地方。 - Jon Skeet

1
我的ASP.NET应用程序 - B2B系统 - 在第一个用户访问时的初始内存为35-40MB。几分钟后,当2或3个用户访问页面时,应用程序的内存会增长到180 MB。
通过阅读.NET开发最佳实践和GC性能指南,我发现问题在于我的应用程序设计。一开始我并不同意这个结论。
我很震惊我们可以犯下如此简单的错误。我放弃了很多功能,并开始简化一些对象。也就是说:
  1. 避免在页面和智能、交互式用户控件中混合使用太多功能(这些控件实际上大多数都存在于使用此控件的每个页面中)。

  2. 停止在基类上生成通用功能。有时候重复是更好的选择。继承是有成本的。

  3. 对于某些复杂功能,我会将所有内容放在同一个函数中。是的,最多可能达到100行代码。当我在 .net 性能指南中看到这个建议时,我并不相信,但它确实有效。调用堆栈是一个问题,使用类属性而不是局部变量也是一个问题。类级别的变量可能会很麻烦。

  4. 停止使用复杂的基类,不存在超过7行的基类。如果你将更大的类分散在整个框架中,你会遇到问题。

  5. 我开始使用更多的静态对象和功能。我看到了另一个人设计的应用程序。所有数据访问对象方法(插入、更新、删除、选择)都是静态的。具有更多并发用户的应用程序从未超过45MB。

  6. 为了节省一些项目,我喜欢稳定状态模式。我在现实世界中学到了这个模式,但作者 Nygard 在他的书《发布 IT - 设计和部署生产就绪软件》中也同意我的看法。他将这种方法称为稳定状态模式。这种模式表明我们可能需要一些东西来释放空闲资源。

  7. 你可能想在机器配置文件中进行一些调整。在 memoryLimit 属性上,你将指示进程重新启动之前可以达到的内存百分比。

  8. 你可能还想在机器配置文件中进行一些调整。在此属性上,GC 将决定机器的行为(工作站 GC 和服务器 GC)。这个选项也可能会极大地改变内存消耗行为。

当我开始关注这些项目时,我取得了很多成功。希望这可以帮到你。

-- 于04-05-2014进行编辑 由于GC新版本的改进和HTML 5以及MVC框架的进步,我改变了许多想法。


我的应用程序与并发用户无关。我不认为我正在使用任何继承。没有用户控件。任何时候都可以使用静态变量,我就会使用它。我的进程从不回收。 - Fantius

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接