使用GC.Collect()有什么问题?


103

尽管我确实理解使用此功能的严重意义(或者至少是我的想法),但我无法理解为什么它成为受尊敬的程序员永远不会使用的东西之一,即使那些甚至不知道的人是为了什么

假设我正在开发一个应用程序,该应用程序的内存使用量会根据用户的操作而变化很大。应用程序的生命周期可以分为两个主要阶段:编辑和实时处理。在编辑阶段,假设创建了数十亿甚至数万亿个对象。其中有些很小,有些则没有,有些可能没有终结器,而有些寿命可能从几毫秒到很长时间不等。接下来,用户决定切换到实时阶段。在这一点上,假设性能起着根本性的作用,并且程序流程中的任何细微改动都可能带来灾难性的后果。然后,通过使用对象池等将对象创建减少到最小程度,但是GC意外插入并扔掉所有对象,然后有人死亡。

问题:在这种情况下,进入第二阶段之前调用GC.Collect()是否明智?

毕竟,这两个阶段永远不会在时间上相互重叠,并且GC可以收集的所有优化和统计信息在这里几乎没有用处...

注意:正如您中某些人指出的那样,.NET可能不是适用于此类应用程序的最佳平台,但这超出了此问题的范围。目的是弄清楚GC.Collect()调用是否可以改善应用程序的整体行为/性能。我们都同意,在这种情况下您会做这种事情的情况极为罕见,但是GC再次尝试猜测并在大多数情况下都做得很好,但这仍然只是猜测。

谢谢。


24
“程序流程中的最细微改动都可能带来灾难性后果……某人可能会死亡”-您确定C#.NET对于您的目的是否具有足够的确定性?
史蒂夫·杰索普

4
Windows和.NET都不是实时平台,因此您不能保证性能指标,至少不足以危及生命。我同意一个人的说法,即您是在夸大还是粗心。
塞尔吉奥·阿科斯塔

3
大声笑“在这些东西中,受人尊敬的程序员永远不会使用,甚至那些甚至都不知道这是干什么的人”!在我的书中,使用不知道为什么的东西的程序员几乎不是最受人尊敬的。:)
The Dag 2013年

Answers:


87

来自Rico的博客...

规则1

别。

这确实是最重要的规则。可以公平地说,大多数使用GC.Collect()都是一个坏主意,我在原始帖子中对此进行了详细介绍,因此在此不再赘述。因此,让我们继续...

规则2

如果某些非重复事件刚刚发生,并且该事件很可能导致许多旧对象死亡,请考虑调用GC.Collect()。

一个典型的例子是,如果您正在编写一个客户端应用程序,并且显示的是一个非常大而复杂的表单,其中包含许多与之相关的数据。您的用户刚刚与该表单进行交互,可能会创建一些大对象……诸如XML文档或一两个大型DataSet之类的东西。当表单关闭时,这些对象已死,因此GC.Collect()将回收与它们关联的内存...

因此,听起来这种情况可能属于第2条规则,您知道在一段时间内许多旧物体已经死亡,而且这种情况不会再次发生。但是,不要忘记Rico的离别词。

在没有充分证据的情况下,规则1应该胜过规则2。

测量,测量,测量。


9
我会说这只是老东西。如果您知道自己在做什么,因此知道何时,如何做以及它的副作用,那么什么都不会是坏的或危险的。永远不要使用xxxx之类的东西放置在这里,以保护世界免受糟糕的程序员侵扰:D
JorgeCórdoba2009年


我并不是说使用GC.Collect是一种好习惯。但是有时候,这是解决问题的快速方法,却不知道其真正原因。我知道这很丑陋,但是确实有效,在我看来这并不是一个坏方法,尤其是在没有太多时间找出问题的根本原因并且您的老板站在您后面的时候……您知道。
沉默寄居者

58

如果在生产代码中调用GC.Collect(),则实际上是在声明您知道更多信息,然后才知道GC的作者。可能是这样。但是通常不是,因此强烈建议不要这样做。


3
的确是这样,但是我不知道他们是否可以做出适用于所有发展的假设。
2012年

2
@Ken不,他们不能。但是,您是否更适合这样做?还是要假设特定的硬件,特定的OS版本等来编写代码?在这一点上,疼痛/增益比过高。
达格2013年

2
我当然是@TheDag IMO。当我释放内存之类的东西时,我实际上并不关心硬件,因为这是操作系统要处理的工作。我也不在乎操作系统,因为我有一个与我正在编程的所有接口相同的接口。(例如,我不在乎是Windows,Mac还是Linux:当我在C / C ++中分配/释放内存时,它是新的/删除了malloc / dealloc)。我总是错的,所以随时纠正我。
2013年

@MasterMastic malloc只有一个非常简单的界面,其实现方式可能有很大的不同。这完全取决于您要解决的问题。如果malloc“足够好”,则不需要缓冲池,对吗?C / C ++开发到处都是示例,您在这些示例中试图对OS /运行时/库进行二次猜测,因为您了解得更多(有时,您确实知道)。许多对性能至关重要的应用程序避免完全使用系统/运行时分配器。游戏用于在启动时预分配所有内存(恒定大小的数组等)。
a安

24

那么,当您使用.NET中的COM对象(如MS Word或MS Excel)时呢?GC.Collect在释放COM对象后没有调用,我们发现Word或Excel应用程序实例仍然存在。

实际上,我们使用的代码是:

Utils.ReleaseCOMObject(objExcel)

' Call the Garbage Collector twice. The GC needs to be called twice in order to get the
' Finalizers called - the first time in, it simply makes a list of what is to be finalized,
' the second time in, it actually does the finalizing. Only then will the object do its 
' automatic ReleaseComObject. Note: Calling the GC is a time-consuming process, 
' but one that may be necessary when automating Excel because it is the only way to 
' release all the Excel COM objects referenced indirectly.
' Ref: http://www.informit.com/articles/article.aspx?p=1346865&seqNum=5
' Ref: http://support.microsoft.com/default.aspx?scid=KB;EN-US;q317109
GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect()
GC.WaitForPendingFinalizers()

那这是对垃圾收集器的不正确使用吗?如果是这样,我们如何使Interop对象死亡?另外,如果不是要像这样使用它,为什么GCCollect方法甚至是Public


3
这将提出一个新的StackOverflow问题,即:如何在不调用GC的情况下清除COM实例。特别是关于非托管循环引用。这是使我不敢将VB6 Outlook加载项升级到C#的挑战之一。(我们做了很多工作来在VB端开发编码模式和测试用例,以确保不再需要COM引用时就可以确定性地杀死它)。
rkagerer 2012年

2
如果这通常适用于COM对象,也许这是一个有效的方案。但是,我要说的是,问题可能出在您使用的是为交互式桌面设计的客户端应用程序作为COM服务器。来自MSDN知识库:“ Microsoft当前不建议,并且不支持通过任何无人参与的非交互式客户端应用程序或组件(包括ASP,ASP.NET,DCOM和NT Services)使Microsoft Office应用程序自动化。在这种环境下运行Office时,可能表现出不稳定的行为和/或死锁。”
达格

2
@TheDag-Microsoft可能不建议,但是我们许多人不得不将带有Office Interop的旧VB6代码移植到.Net Windows应用程序。我花了几个月的时间,直到最终摆脱了从大型VB6到.Net转换项目的所有隐藏的引用。不过,学会以相反的分配顺序发布并持有对包含集合的每个com对象的本地引用都很有帮助。
Dib 2014年

15

好吧,GC是我之间有爱/恨关系的事情之一。我们过去已经打破了通过VistaDB并在博客上对此进行了博客。他们已修复此问题,但需要很长时间才能从他们那里得到有关此类问题的修复程序。

GC非常复杂,很难一一对应所有方法。MS的工作做得相当不错,但有时可能会欺骗GC。

通常,您不应该添加a,Collect除非您知道事实是您刚刚转储了很多内存,如果GC现在不清理它,它将陷入中期危机

您可能会用一系列错误的GC.Collect陈述来破坏整个机器。对collect语句的需求几乎总是指向更大的基础错误。内存泄漏通常与引用有关,并且对它们的工作原理缺乏了解。或者使用IDisposable不需要的on对象,并给GC施加更高的负载。

通过系统性能计数器密切关注在GC中花费的时间百分比。如果您在GC中看到您的应用使用了20%或更多的时间,则表示您存在严重的对象管理问题(或异常的使用模式)。您希望始终尽量减少GC花费的时间,因为它可以加快整个应用程序的速度。

还需要注意的是,服务器上的GC与工作站上的GC不同。我已经看到了一些难以发现问题的小难题,因为人们没有测试他们两个(甚至都不知道他们是两个)。

为了尽可能全面地回答我,如果您也针对该平台,也应该在Mono下进行测试。由于它是完全不同的实现,因此可能会遇到与MS实现完全不同的问题。


罪魁祸首通常是事件。每当将实例方法用作事件处理程序时,事件的发布者都会通过事件委托对订阅者进行引用。避免出现此问题的唯一“简便”方法是仅使用寿命最长与订阅者一样长的发布者(例如,一个TextBox发布由包含表单处理的事件是没有问题的,因为不应假定该Textbox住在表格之外)。问题场景示例:Singleton模型,用于处理模型事件的临时视图。
达格

5
一个人怎么弄糟整个机器?
亚当·格雷

13

在某些情况下它很有用,但通常应避免使用它。您可以将其与GOTO进行比较,也可以将其与轻便摩托车相提并论:在需要时可以这样做,但是您不会告诉朋友。


12

根据我的经验,从来不建议在生产代码中调用GC.Collect()。是的,在调试中,它具有帮助澄清潜在内存泄漏的优点。我想我的根本原因是程序员已经比我聪明得多地编写和优化了GC,如果我觉得我需要调用GC.Collect()的话,这是我走了一条路的线索某处。在您的情况下,听起来好像并没有实际的内存问题,只是您担心集合将给您的过程带来什么不稳定性。看到它不会清除仍在使用的对象,并且可以非常迅速地适应不断增长的需求和降低的需求,我想您将不必担心。


10

调用GC.Collect()的最大原因之一是,您刚刚执行了一个重大事件,该事件会产生大量垃圾,例如您所描述的。在这里调用GC.Collect()可能是个好主意;否则,GC可能无法理解这是一次“一次性”事件。

当然,您应该对其进行概要分析,然后自己看看。


9

好吧,显然,您不应该使用具有非实时垃圾收集的语言编写具有实时需求的代码。

在阶段定义明确的情况下,触发垃圾收集器没有问题。但是这种情况极为罕见。问题是,许多开发人员将尝试使用这种方法以杂技风格掩盖纸质问题,并且不加选择地添加它会导致性能问题。


真正。但是能够捕获错误条件“​​对象不符合垃圾收集但应该是”的自动测试将很有价值。我可以通过工厂逻辑,析构函数逻辑和GC.Collect的组合来实现。例如,您的Entity类具有IObjectTracker属性,通常为null,但由测试用途实体工厂分配。工厂还将通知跟踪器对象的出生,而析构函数将其通知(如果存在)死亡。如果您知道“析构函数已对所有垃圾收集对象执行了”,则可以检查跟踪器状态以检测泄漏。
达格

7

调用GC.Collect()会强制CLR进行堆栈遍历,以查看是否可以通过检查引用来真正释放每个对象。如果对象数量很多,这将影响可伸缩性,并且众所周知,它经常触发垃圾回收。信任CLR,并让垃圾回收器在适当的时候自行运行。


2
不仅会导致堆栈游走,而且还会冻结应用程序的主线程(及其创建的任何子线程),以便GC 可以遍历堆栈。您的应用在GC中花费的时间越长,冻结的时间就越多。
Scott Dorman

3
我更担心由于内存不足异常导致的应用程序崩溃而不是性能降低,因为应用程序/ GC抛出了不再需要的东西。有人知道为什么Microsoft似乎会抛出OOM异常而不首先抛出垃圾吗?(如果没有这种明显的阶梯-或者至少是为什么这一步没有出现的解释抛出OOM例外,我不知道我有事情发生“自动”的任何信仰之前尝试“他们应该这样。”
Wonderbird

6

实际上,我认为调用GC.Collect并不是一个很糟糕的做法。
在某些情况下,我们需要这样做。举例来说,我有一个运行线程的表单,该表单依次打开数据库中的不同表,将BLOB字段中的内容提取到临时文件中,对该文件进行加密,然后将该文件读入二进制流,然后再返回到BLOB中。另一个表中的字段。

整个操作会占用大量内存,并且不确定表中的行数和文件内容的大小。

我以前经常会收到OutofMemory异常,我认为定期根据计数器变量运行GC.Collect是明智的。我增加一个计数器,当达到指定级别时,将调用GC来收集可能形成的所有垃圾,并回收由于意外的内存泄漏而丢失的任何内存。

在此之后,我认为它运行良好,至少没有例外!!!
我通过以下方式致电:

var obj = /* object utilizing the memory, in my case Form itself */
GC.Collect(GC.GetGeneration(obj ,GCCollectionMode.Optimized).

5

在.net下,执行垃圾收集所需的时间与不是垃圾的数量紧密相关,而与不是垃圾的数量紧密相关。的确,除非对象被覆盖Finalize(显式地或通过C#析构函数覆盖),否则它是a的目标WeakReference,位于大对象堆上,或者以某些其他与gc相关的方式是特殊的,唯一可以标识其所在的内存作为对象是存在对其的根引用。否则,GC的操作类似于从一栋建筑物中取出所有有价值的东西,并炸毁该建筑物,在旧建筑物的场地上建造新建筑物,然后将所有有价值的物品放入其中。炸药建筑物所需的精力完全独立于建筑物内的垃圾量。

因此,呼叫GC.Collect易于增加系统必须完成的全部工作量。它将延迟下一个集合的发生,但可能会立即执行下一个集合所需的工作量。在进行下一次收集时,收集所花费的总时间将与GC.Collect未调用的时间大致相同,但是系统将积累一些垃圾,从而导致需要比GC.Collect不进行收集更快的后续收集被称为。

我看到GC.Collect真正有用的时候是需要测量某些代码的内存使用量(因为内存使用量数字仅在收集之后才真正有意义),或者概述几种算法中的哪一种更好(调用GC.Collect()在运行每段代码之前,可以帮助确保基线状态一致)。在其他情况下,有些人可能不知道GC,但除非编写一个单线程程序,否则无法知道GC.Collect调用将帮助一个线程的数据结构避免“中年危机”不会导致其他线程的数据出现“中年危机”,而这种情况本可以避免。


5

循环创建图像-即使调用dispose,也不会恢复内存。每次都收集垃圾。我将照片处理应用程序上的1.7GB内存增加到24MB,性能非常好。

绝对有时间需要调用GC.Collect。


2
呼叫Dispose不应该释放托管内存。您似乎不知道.NET中的内存模型如何工作。
安德鲁·巴伯

4

我们有一个类似的问题,即垃圾收集器没有收集垃圾并释放内存。

在我们的程序中,我们正在使用OpenXML处理一些大小适中的Excel电子表格。电子表格包含5到10个“工作表”,大约1000行14列。

32位环境(x86)中的程序将崩溃,并显示“内存不足”错误。我们确实让它在x64环境中运行,但是我们想要一个更好的解决方案。

我们找到了一个。

以下是一些简化的代码片段,这些代码片段在显式调用垃圾回收器以释放已处置对象中的内存时不起作用和不起作用。

从子例程内部调用GC无效。记忆从未被收回...

For Each Sheet in Spreadsheets
    ProcessSheet(FileName,sheet)
Next

Private Sub ProcessSheet(ByVal Filename as string, ByVal Sheet as string)
    ' open the spreadsheet 
    Using SLDoc as SLDocument = New SLDocument(Filename, Sheet)
        ' do some work....
        SLDoc.Save
    End Using
    GC.Collect()
    GC.WaitForPendingFinalizers()
    GC.Collect()
    GC.WaitForPendingFinalizers()
End Sub

通过将GC调用移到该子例程的范围之外,可以收集垃圾并释放内存。

For Each Sheet in Spreadsheets
    ProcessSheet(FileName,sheet)
    GC.Collect()
    GC.WaitForPendingFinalizers()
    GC.Collect()
    GC.WaitForPendingFinalizers()
Next

Private Sub ProcessSheet(ByVal Filename as string, ByVal Sheet as string)
    ' open the spreadsheet 
    Using SLDoc as SLDocument = New SLDocument(Filename, Sheet)
        ' do some work....
        SLDoc.Save
    End Using
End Sub

我希望这有助于对.NET垃圾回收感到沮丧的其他人,因为它似乎忽略了对.NET的调用GC.Collect()

保罗·史密斯


4

显式调用集合没有错。有些人只是真的想相信,如果这是供应商提供的服务,请不要质疑它。哦,所有这些随机冻结都在交互式应用程序的错误时刻出现了吗?下一版本将使其更好!

确实,让后台进程处理内存操作意味着不必自己处理。但这在逻辑上并不意味着我们最好不要在任何情况下都自己处理它。GC已针对大多数情况进行了优化。但这在逻辑上并不意味着在所有情况下都对其进行了优化。

您是否曾经用明确的答案回答过一个开放式问题,例如“哪种是最好的排序算法”?如果是这样,请勿触摸GC。对于那些询问条件或给出“在这种情况下”键入答案的人,您可以继续学习GC以及何时激活它。

一定要说,我在Chrome和Firefox中冻结了应用程序,这使我感到沮丧,即使在某些情况下,内存的增长也不受阻碍-如果只有他们学会了调用垃圾收集器-或给我一个按钮,以便在我开始阅读页面文字时可以点击它,因此在接下来的20分钟之内不会出现冻结。



2

它出什么问题了?您正在对垃圾回收器和内存分配器进行第二次猜测,在它们之间,您对应用程序在运行时的实际内存使用情况的了解要比您大得多。


1
垃圾收集器的启发式性质以及它们将此功能公开给外界的事实使我认为,如果在需要的地方使用它,它将很有用。问题不在于使用它,而是知道如何,何时何地使用它。
陷阱

更不用说GC对其他所有应用程序及其内存需求的更好了解。GC与操作系统协商内存,因此受可用物理内存以及计算机上所有托管和非托管进程的影响。尽管我怀疑GC确实知道“何时进行收集的最佳时机”,但总体而言,与任何单个应用程序相比,它都有更好的策略。;)
The Dag 2013年

2

调用GC.Collect()的愿望通常是试图掩盖您在其他地方犯的错误!

如果您发现您忘记了不再需要的东西,那会更好。


5
也许那是一个泛化
MickyD

1

最重要的是,您可以分析应用程序并查看这些其他集合如何影响事物。我建议您不要使用它,除非您要进行简介。GC旨在照顾好自己,随着运行时间的发展,它们可能会提高效率。您不希望到处乱七八糟的代码可能会破坏工作,并且无法利用这些改进。使用foreach而不是for有一个类似的论点,那就是,可以在foreach下添加将来的改进,并且不必更改代码即可利用。


1

.NET Framework本身从未设计为可以在实时环境中运行。如果您确实需要实时处理,则可以使用不基于.NET的嵌入式实时语言,也可以使用Windows CE设备上运行的.NET Compact Framework。


他可能正在使用WAS专为实时环境设计的.Net Micro Framework。
TraumaPony

@TraumaPony:检查表在本页面底部msdn.microsoft.com/en-us/embedded/bb278106.aspx:显然,微架构不是专为实时环境。但是,它是为嵌入式环境(如WinCE)设计的,但功耗较低。
Scott Dorman

1

最糟糕的是会使您的程序冻结一段时间。因此,如果您满意,请执行此操作。通常,对于大多数用户交互的胖客户端或Web应用程序来说,不需要使用它。

我发现,有时具有长时间运行的线程的程序或批处理程序,即使正确地布置对象,也会出现OutOfMemory异常。我记得一个是业务线数据库事务处理;另一个是胖客户端应用程序中后台线程上的索引例程。

在这两种情况下,结果都很简单:没有GC.Collect,内存不足,一致;GC.Collect,完美无缺的性能。

我已经尝试过几次来解决内存问题,但无济于事。我拿出来了

简而言之,除非遇到错误,否则请勿放入。如果您将其放入并不能解决内存问题,请将其取出。记住要在发布模式下进行测试,并将苹果与苹果进行比较。

唯一会出错的地方是您对此有道理。这不是价值观问题;许多程序员已经死了,并通过许多不必要的GC直奔天堂。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.