109

尽管我确实理解使用此功能的严重影响(或者至少我是这么认为的),但我不明白为什么它会成为受人尊敬的程序员永远不会使用的东西之一,即使是那些甚至不知道的人它是干什么用的。

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

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

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

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

谢谢。

4

20 回答 20

90

来自 Rico 的博客...

规则1

不。

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

规则 #2

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

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

所以听起来这种情况可能属于第 2 条规则,你知道有一段时间很多旧对象已经死亡,而且它不会重复出现。但是,不要忘记 Rico 的离别词。

在没有强有力证据的情况下,规则 #1 应该胜过规则 #2。

测量,测量,测量。

于 2008-09-23T02:15:16.947 回答
58

如果您在生产代码中调用 GC.Collect() ,您实际上是在声明您比 GC 的作者了解更多。情况可能就是这样。然而,它通常不是,因此强烈建议不要这样做。

于 2008-09-23T01:49:20.160 回答
26

那么,当您使用来自 .NET 的 MS Word 或 MS Excel 等 COM 对象时又如何呢?释放 COM 对象后不调用GC.Collect,我们发现 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()

那么这会是对垃圾收集器的错误使用吗?如果是这样,我们如何让互操作对象死掉?此外,如果它不打算这样使用,为什么GC'Collect方法甚至Public

于 2012-01-12T15:30:57.230 回答
16

好吧,GC是我爱/恨关系的那些东西之一。我们过去通过 VistaDB 打破了它,并在博客中介绍了它。他们已经修复了它,但是需要很长时间才能从他们那里获得对此类问题的修复。

GC 很复杂,在这么大的东西上,一刀切的方法是非常非常难以实现的。MS 在这方面做得相当不错,但有时可能会欺骗 GC。

一般来说,你不应该添加 a Collect,除非你知道你刚刚转储了大量内存,如果 GC 现在不清理它,它将进入中年危机。

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

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

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

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

于 2008-09-23T03:09:26.083 回答
14

在某些情况下它很有用,但通常应该避免它。您可以将其与 GOTO 或骑轻便摩托车进行比较:您在需要时执行此操作,但您不会告诉您的朋友。

于 2008-09-23T01:39:49.573 回答
12

根据我的经验,在生产代码中调用 GC.Collect() 是不可取的。在调试中,是的,它有助于澄清潜在的内存泄漏。我想我的根本原因是 GC 是由比我聪明得多的程序员编写和优化的,如果我觉得我需要调用 GC.Collect() 这表明我已经偏离了道路某处。在您的情况下,听起来您实际上并没有内存问题,只是您担心集合会给您的流程带来什么不稳定性。看到它不会清除仍在使用的对象,并且它可以非常迅速地适应上升和下降的需求,我想你不必担心它。

于 2008-09-23T01:40:58.307 回答
10

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

当然,您应该对其进行分析,并亲自查看。

于 2008-09-23T02:07:19.023 回答
9

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

在具有明确定义的阶段的情况下,触发垃圾收集器没有问题。但这种情况极为罕见。问题是很多开发者会尝试用它来以货物崇拜的方式来解决问题,并且不加选择地添加它会导致性能问题。

于 2008-09-23T01:36:20.423 回答
7

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

于 2008-09-23T01:55:53.480 回答
6

事实上,我不认为调用 GC.Collect 是一个非常糟糕的做法。
可能有我们需要的情况。例如,我有一个运行线程的表单,该线程反过来打开数据库中的不同表,将 BLOB 字段中的内容提取到临时文件,加密文件,然后将文件读入二进制流并返回 BLOB另一个表中的字段。

整个操作占用了相当多的内存,并且不确定表格中文件内容的行数和大小。

我曾经经常遇到 OutofMemory Exception,我认为基于计数器变量定期运行 GC.Collect 是明智之举。我增加一个计数器,当达到指定的级别时,调用 GC 来收集可能已经形成的任何垃圾,并回收由于不可预见的内存泄漏而丢失的任何内存。

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

var obj = /* object utilizing the memory, in my case Form itself */
GC.Collect(GC.GetGeneration(obj ,GCCollectionMode.Optimized).
于 2013-03-20T15:20:24.210 回答
5

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

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

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

于 2012-05-13T16:31:56.990 回答
5

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

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

于 2012-10-22T20:52:34.957 回答
5

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

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

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

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

于 2017-04-03T22:18:32.537 回答
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()

保罗·史密斯

于 2014-02-05T15:32:01.893 回答
2

我认为你对这个场景是正确的,但我不确定 API。

微软表示,在这种情况下,您应该添加内存压力作为 GC 的提示,它应该很快执行一次收集。

于 2008-09-23T01:33:56.093 回答
2

它出什么问题了?事实上,你在猜测垃圾收集器和内存分配器,它们之间比你更清楚地了解应用程序在运行时的实际内存使用情况。

于 2008-09-24T23:14:29.157 回答
2

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

如果您找到忘记丢弃不再需要的东西的地方会更好。

于 2008-10-19T17:23:38.450 回答
2

它会做的最糟糕的事情是让你的程序冻结一点。所以,如果你没问题,那就去做吧。通常,对于主要是用户交互的胖客户端或 Web 应用程序不需要它。

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

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

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

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

唯一可能出现问题的情况是当你对它有道德的时候。这不是价值观问题;许多程序员已经死去,他们的代码中有许多不必要的 GC.Collects,它们比他们更长寿。

于 2012-08-17T01:19:25.740 回答
1

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

于 2008-09-23T01:41:38.070 回答
1

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

于 2008-09-23T03:25:24.187 回答