是否进行 GC.Collect?
阅读这份古老但经典的文档编写高性能托管应用程序 - 入门 ,我遇到了以下声明
GC 是自我调整的,会根据应用程序的内存需求进行自我调整。在大多数情况下,以编程方式调用 GC 会阻碍这种调整。通过调用 GC.Collect 来“帮助”GC 很可能不会提高应用程序的性能
我正在处理在给定时间点消耗大量内存的应用程序。当我完成代码消耗该内存时,我将调用 GC.Collect。如果我不这样做,我就会出现内存不足异常。这种行为不一致,但大约 30% 的情况下,我会出现内存不足的情况。添加 GC.Collect 后,我再也没有遇到过内存不足异常。即使本最佳实践文件建议反对,我的行为是否合理?
Reading this old but classic document Writing High-Performance Managed Applications - A Primer, I came across following statment
The GC is self-tuning and will adjust itself according to applications memory requirements. In most cases programmatically invoking a GC will hinder that tuning. "Helping" the GC by calling GC.Collect will more than likely not improve your applications performance
I am working with applications that during a given point in time, consumes a lot of memory. When I am done in code consuming that memory, I am calling GC.Collect. If I don't do it I get Out of memory exception. This behaviour is inconistent but roughtly speaking 30% of the time, I get an out of memory. After adding GC.Collect I never got this out of memory exception. Is my action justified even though this best practice document is advising against it?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
GC 中发生的部分情况是内存中的对象是分代的,因此早期代的收集比其他代更频繁。这有助于通过不尝试一直收集长期存在的对象来节省性能。
考虑到这一点,当您自己调用 GC.Collect() 时可能会发生两种情况。首先,您最终会花费更多时间进行收藏。这是因为除了手动 GC.Collect() 之外,正常的后台收集仍然会发生。第二个是你会保留内存更长时间,因为你将一些不需要去那里的东西强行放入了更高阶的生成中。换句话说,自己使用 GC.Collect() 几乎总是一个坏主意。
在某些情况下,垃圾收集器并不总是表现良好。其中之一是大对象堆。这是针对大于特定大小(80,000 字节,IIRC,但现在可能已经过时)的对象的特殊生成。这一代几乎从未被收集过,也几乎从未被压缩过。这意味着随着时间的推移,内存中可能会出现许多无法释放的相当大的漏洞。物理内存并未实际使用,并且可用于其他进程,但它仍然会消耗进程内的地址空间,默认情况下,地址空间限制为 2GB。
这是 OutOfMemory 异常的一个非常常见的来源 - 您实际上并没有使用那么多内存,但是所有这些地址空间都被大对象堆中的漏洞占用了。到目前为止,发生这种情况的最常见方式是重复附加到大字符串或文档。这可能不是你的问题,因为在这种情况下,对 GC.Collect() 的任何调用都不会压缩 LOH,但在你的情况下,它似乎确实有帮助。然而,这是我见过的绝大多数 OutOfMemory 异常的根源。
垃圾收集器并不总是表现良好的另一个地方是当某些事情导致对象保持根状态时。一个例子是事件处理程序可以阻止对象被收集。解决此问题的方法是确保订阅事件的每个
+=
操作都有相应的-=
操作来取消订阅。但同样,GC.Collect() 在这里不太可能有帮助 - 对象仍然扎根于某个地方,因此无法被收集。希望这为您提供了一种调查途径来解决导致需要首先使用 GC.Collect() 的根本问题。但如果没有,当然,有一个可行的程序比一个失败的程序要好。无论我在哪里使用 GC.Collect(),我都会确保代码有完整的文档记录,其中包含您需要它的原因(如果没有,您会遇到异常)以及可靠地重现它所需的准确步骤和数据这样未来可能想要删除它的程序员就可以确定何时可以安全地这样做。
Part of what goes on in the GC is that objects in memory are generational, such that early generations are collected more frequently than others. This helps save performance by not trying to collect long-lived objects all the time.
With that in mind, two things can happen when you call
GC.Collect()
yourself. The first is that you end up spending more time doing collections. This is because the normal background collections will still happen in addition to your manual GC.Collect(). The second is that 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.There are a few cases where the garbage collector doesn't always perform well. One of these is the large object heap. This is a special generation for objects larger than a certain size (80,000 bytes, IIRC, but that could be old now). This generation is hardly ever collected and almost never compacted. That means that over time you can end up with many sizable holes in memory that will not be released. The physical memory is not actually used and is available for other processes, but it does still consume address space within your process, of which you are limited to 2GB by default.
This is a very common source for OutOfMemory exceptions — you're not actually using that much memory, but you have all this address space taken up by holes in the large object heap. By far the most common way this happens is repeatedly appending to large strings or documents. This probably is not you, because in this scenario no amount of calls to GC.Collect() will companct the LOH, but in your case it does seem to help. However, this is the source for the vast majority of the OutOfMemory exceptions I've seen.
Another place where the garbage collector does not always perform well is when certain things cause objects to remain rooted. One example is that event handlers can prevent an object from being collected. A way around this is make sure that every
+=
operation to subscribe an event has a corresponding-=
operation to unsubscribe it. But again, a GC.Collect() is unlikely to help here - the object is still rooted somewhere, and so can't be collected.Hopefully this gives you an avenue of investigation to solve your underlying problem that causes the need to use GC.Collect() in the first place. But if not it is, of course, better to have a working program than a failing program. Anywhere I do use GC.Collect(), I would make sure the code is well documented with the reason why you need it (you get exceptions without) and the exact steps and data required to reproduce it reliably so that future programmers who may want to remove this can know for sure when it is safe to do so.
大多数人会说让代码正确运行比让它运行得更快更重要。因此,当您不调用 GC.Collect() 时,它有 30% 的时间无法工作,那么这就超越了所有其他问题。
当然,这会导致更深层次的问题:“为什么会出现 OOM 错误?是否有更深层的问题需要解决,而不是仅仅调用 GC.Collect()。
但是您发现的建议如果性能使您的应用程序有 30% 的时间失败,您是否关心性能?
Most people would say that making your code work correctly is more important than making it fast. Thus, it it fails to work 30% of the time when you don't call
GC.Collect()
, then that trumps all other concerns.Of course, that leads to the deeper question of "why do you get OOM errors? Is there a deeper issue that should be fixed, instead of just calling
GC.Collect()
.But the advice you found talks about performance. Do you care about performance if it makes your app fail 30% of the time?
一般来说,
GC.Collect
应该不是必需的。如果您的图像存在于非托管内存中,请务必使用GC.AddMemoryPressure
和GC.RemoveMemoryPressure
适当。Generally speaking,
GC.Collect
shouldn't be necessary. If your images exist in unmanaged memory, then be sure to useGC.AddMemoryPressure
andGC.RemoveMemoryPressure
appropriately.从您的描述来看,听起来
Dispose
able对象没有被释放,或者您没有设置在操作之前将被替换为null的成员值。作为后者的示例:您可以暂时清除网格,因为它即将被刷新无论如何都要更换;您将暂时将这两个表保留在内存中(不必要),如果不这样做,则会将其替换。
From your description it sounds like
Dispose
able objects are not being disposed, or you're not setting member values that will be replaced to null before the operation. As an example of the latter:You can clear out the grid in the interim since it's about to be replaced anyway; you will temporarily have both tables in memory (unnecessarily) while it is replaced if you don't.