使用GC.Collect()有什么不对?

Tra*_*rap 102 .net performance garbage-collection memory-management

虽然我确实理解玩这个功能的严重影响(或者至少是我的想法),但我不明白为什么它会成为那些受人尊敬的程序员不会使用的东西之一,即使是那些甚至不知道的人也是如此它是什么.

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

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

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

注意:正如你们中的一些人所指出的那样,.NET可能不是这样的应用程序的最佳平台,但这超出了这个问题的范围.目的是澄清GC.Collect()调用是否可以改善应用程序的整体行为/性能.我们都同意你在这种情况下做这种事情的情况非常罕见,但话说再次,GC试图猜测并且在大多数情况下做得非常好,但它仍然是猜测.

谢谢.

Jon*_*ton 87

来自Rico的博客......

规则1

别.

这确实是最重要的规则.可以说GC.Collect()的大多数用法都是一个坏主意,我在原始帖子中详细介绍了这一点,所以我不会在这里重复所有这些.那么让我们继续......

规则#2

如果刚刚发生了一些非经常性事件,请考虑调用GC.Collect(),并且此事件很可能导致许多旧对象死亡.

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

所以听起来这种情况可能属于规则#2,你知道有很多旧物品已经死亡的时刻,并且它是非经常性的.但是,不要忘记Rico的离别词.

规则#1应该胜过规则#2而没有强有力的证据.

测量,测量,测量.

  • 我会说这只是旧事.如果你知道你在做什么,因此知道何时以及如何做,以及它的副作用,没有什么是真正的坏或危险.永远不会使用xxxx之类的事情被放在那里以保护世界免受糟糕的程序员:D (9认同)

Aar*_*her 56

如果你在生产代码中调用GC.Collect(),你基本上宣称你比GC的作者更了解.情况可能就是这样.然而,它通常不是,因此强烈劝阻.

  • 这是非常正确的,但我不知道他们是否可以做出适用于所有发展的假设. (3认同)
  • @Ken不,他们不能.但是你处于更好的位置吗?或者你打算编写假设特定硬件,特定操作系统版本等的代码?这个的疼痛/增益比率太高了. (2认同)
  • @TheDag IMO当然是我.当我释放内存时,我并不真正关心硬件,因为那是处理这个问题的操作系统工作.我也不关心操作系统,因为我有一个与我正在编程的所有连接相同的接口.(例如,我不关心它是Windows,Mac还是Linux:当我在C/C++中分配/释放内存时,它是新的/删除malloc/dealloc).我总是错的,所以请随意纠正我. (2认同)

Dib*_*Dib 23

那么当您使用.Net的MS Word或MS Excel等COM对象时呢?在不调用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()
Run Code Online (Sandbox Code Playgroud)

那么这是不正确使用垃圾收集器吗?如果是这样,我们如何让Interop对象死掉?此外,如果并不意味着它像这样被使用,为什么是GCCollect方法,甚至Public

  • 这将成为一个很好的新StackOverflow问题,即:如何在不调用GC的情况下根除COM实例.特别是关于非托管循环引用.这是让我担心将我的VB6 Outlook加载项升级到C#的挑战之一.(我们做了很多工作来开发VB端的编码模式和测试用例,确保COM引用在不再需要时以确定的方式被杀死). (3认同)
  • 如果这通常适用于COM对象,那么这可能是一个有效的方案.但是,我想说问题很可能是你使用专为交互式桌面设计的客户端应用程序作为COM服务器.来自MSDN知识库:"Microsoft目前不推荐也不支持从任何无人参与的非交互式客户端应用程序或组件(包括ASP,ASP.NET,DCOM和NT服务)自动化Microsoft Office应用程序,因为Office Office在此环境中运行时可能会出现不稳定的行为和/或死锁." (2认同)
  • @TheDag - 微软可能不推荐,但是我们中的许多人不得不将旧的VB6代码与办公室互操作移植到.Net windows应用程序.我花了几个月的工作,直到我最终摆脱了一个大的VB6到.Net转换项目的所有隐形挂起参考.学习以反向分配顺序发布并将本地引用保存到包括集合在内的每个单个com对象都有帮助. (2认同)

Jas*_*ort 15

好吧,GC是我与之有爱/恨关系的事情之一.我们过去通过VistaDB 打破了它,并在博客上发表过关于它的信息.他们已经修复了它,但需要很长时间才能从这些事情中获得修复.

GC是复杂的,一个适合所有方法的非常非常难以实现这么大的东西.MS做得相当不错,但有时可能会愚弄GC.

一般情况下,你不应该添加一个,Collect除非你知道一个事实,你只是倾倒了大量的内存,如果GC现在没有清理它会导致中年危机.

你可以通过一系列糟糕的GC.Collect陈述搞砸整个机器.对collect语句的需求几乎总是指向更大的底层错误.内存泄漏通常与引用有关,也缺乏对它们如何工作的理解.或者使用IDisposable不需要它的on对象并在GC上施加更高的负载.

仔细观察通过系统性能计数器在GC中花费的时间百分比.如果您在GC中使用20%或更多时间看到您的应用程序,则会出现严重的对象管理问题(或异常使用模式).您希望始终最小化GC花费的时间,因为它会加快您的整个应用程序.

同样重要的是要注意GC在服务器上与工作站不同.我已经看到一些小的难以追踪的问题,人们没有测试他们两个(或甚至不知道他们是他们两个).

尽管我的答案尽可能完整,但你也应该在Mono下进行测试,如果你也是针对那个平台的话.由于它是完全不同的实现,因此可能遇到与MS实现完全不同的问题.

  • 如何搞砸整个机器? (5认同)

rjo*_*ton 13

有些情况下它很有用,但一般情况下应该避免.您可以将它与GOTO或骑摩托车进行比较:您可以在需要时进行比较,但是您没有告诉您的朋友.


The*_*ker 12

根据我的经验,从来没有建议在生产代码中调用GC.Collect().在调试中,是的,它有助于澄清潜在的内存泄漏.我想我的根本原因是程序员已经编写并优化了GC,比我更聪明,如果我达到了一个我觉得我需要调用GC.Collect()的一点,那就是我走了路的线索某处.在您的情况下,听起来您实际上没有内存问题,只是您担心集合会给您的流程带来什么不稳定性.看到它不会清理仍在使用的物体,并且它能够很快适应上升和下降的要求,我认为你不必担心它.


Tra*_*ony 10

调用GC.Collect()的最大原因之一就是当你刚刚执行了一个会产生大量垃圾的重要事件时,例如你描述的内容.调用GC.Collect()在这里是一个好主意; 否则,GC可能不会理解这是"一次性"事件.

当然,你应该对它进行描述,并亲自看看.


wno*_*ise 9

好吧,显然你不应该用非实时垃圾收集的语言编写具有实时要求的代码.

在具有明确定义的阶段的情况下,触发垃圾收集器没有问题.但这种情况极为罕见.问题在于,许多开发人员将尝试使用它来解决货物崇拜风格的问题,并且不加选择地添加它会导致性能问题.


kd7*_*kd7 7

调用GC.Collect()会强制CLR执行堆栈遍历,以查看是否可以通过检查引用来真正释放每个对象.如果对象的数量很多,这将影响可伸缩性,并且还已知太频繁地触发垃圾收集.信任CLR并让垃圾收集器在适当时自行运行.

  • 我更关心的一个应用程序崩溃是由于内存不足异常不是慢的性能,因为应用程序/ GC被扔的是,不再需要的东西.有没有人知道为什么微软似乎在没有FIRST抛出垃圾的情况下抛出OOM异常?(没有这个显而易见的步骤 - 或至少解释为什么在抛出OOM异常之前似乎没有尝试过这个步骤我不确定我对"自动""他们应该采用的方式"发生的事情有任何信心. (3认同)
  • 您不仅会导致堆栈遍历,而且您的应用程序主线程(以及它创建的任何子线程)都被冻结,因此GC**可以**遍历堆栈.您的应用花费在GC上的时间越长,花费的时间就越长. (2认同)

小智 6

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

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

  • 调用`Dispose` 不应该*释放托管内存。您似乎不知道 .NET 中的内存模型是如何工作的。 (3认同)

Ven*_*l M 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).
Run Code Online (Sandbox Code Playgroud)


sup*_*cat 5

在.net下,执行垃圾收集所需的时间与不是垃圾的东西的数量密切相关,而不是与垃圾收集的数量相关.实际上,除非一个对象覆盖Finalize(显式地,或通过C#析构函数),是a的目标WeakReference,坐在大对象堆上,或者在某些其他与gc相关的方式中是特殊的,唯一能识别它所在的内存的东西作为一个对象是存在对它的rooted引用.否则,GC的操作类似于从建筑物中取出所有有价值的物品,并对建筑物进行炸毁,在旧建筑物的旧址上建造新的建筑物,并将所有有价值的物品放入其中.炸毁建筑物所需的努力完全独立于其内部的垃圾量.

因此,呼叫GC.Collect很容易增加系统必须完成的工作量.它会延迟下一个集合的发生,但可能会在下一个集合发生时立即执行同样多的工作; 在下一次收集发生时,收集的总时间与GC.Collect未调用的时间大致相同,但系统会累积一些垃圾,导致后续收集需要的时间比GC.Collect没有被称为.

我可以看到GC.Collect真正有用的时间是,当需要测量某些代码的内存使用量时(因为内存使用数字在集合之后才真正有意义),或者描述几种算法中的哪一种更好(调用GC.Collect())在运行几段代码之前,可以帮助确保一致的基线状态).在其他一些情况下,人们可能知道GC没有的东西,但除非有人编写单线程程序,否则人们无法知道GC.Collect有助于一个线程的数据结构避免"中年危机" 的调用"不会导致其他线程的数据产生"中年危机",否则这些危机就会被避免.


小智 5

我们遇到了类似的问题,垃圾收集器不收集垃圾并释放内存。

在我们的程序中,我们使用 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
Run Code Online (Sandbox Code Playgroud)

通过将 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
Run Code Online (Sandbox Code Playgroud)

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

保罗·史密斯


Ger*_*ill 5

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

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

你有没有回答过一个开放的问题,比如“哪个是最好的排序算法”,并给出了明确的答案?如果是这样,请不要触摸 GC。对于那些询问条件或给出“在这种情况下”类型答案的人,您可以继续了解 GC 以及何时激活它。

不得不说,我在 Chrome 和 Firefox 中遇到过应用程序冻结,这让我很沮丧,即使在某些情况下,内存也会不受阻碍地增长——如果他们学会调用垃圾收集器——或者给我一个按钮,这样当我开始阅读页面的文本时,我可以点击它,从而在接下来的 20 分钟内不会冻结。