【问题标题】:Why do I have to call GC collect explicitly?为什么我必须显式调用 GC collect?
【发布时间】:2013-04-15 01:32:16
【问题描述】:

我创建了一个示例 C# 控制台应用程序,它读取字节数组中的文件数据并将字节数组转换为十六进制字符串。它需要巨大的内存,并且在工作完成后它不会释放内存,我还将正在使用的变量无效。

这里是示例代码:

string filename = @"F:\\AVSEQ09.DAT"; //file size is 32 MB
string hexData = null;

byte[] fileDataContent = File.ReadAllBytes(filename);
if (fileDataContent != null)
    hexData = BitConverter.ToString(fileDataContent);
fileDataContent = null;
hexData = null;

//GC.Collect();
Console.ReadKey();

如果我运行此代码,它需要 433 MB 的私有工作集,如果我取消注释 GC.collect 调用,则内存将降至 6 MB。为什么我必须显式调用 GC.collect,显式调用 GC.collect 是否不好,如何在不调用 GC.collect 的情况下释放内存(至 6 MB)?

【问题讨论】:

  • "为什么我必须显式调用 GC collect" - 你不需要。而且不应该。
  • 之前也问了无数次....
  • 是的,确实如此。 @MitchWheat
  • 你真的必须一次读取整个文件吗?
  • GC 最终会释放它(假设您不断分配足够的新对象)。如果您想使用您的额外信息及时释放它,您需要手动调用它。但我推荐GCCollectionMode.Optimized

标签: c# .net


【解决方案1】:

Garbage collection is the simulation of infinite memory on a machine with finite memory,通过回收有效程序无法注意到的内存丢失。

以上是一个非常重要的概念,因为除其他外,它强调了这样一个事实,即垃圾收集器必须做任何事情,只要它可以在您的程序每次请求时提供内存它。

它可能不是最可控的内存管理系统,但如果你的程序最终处于内存压力的情况下,CLR 通常会自动启动垃圾回收周期来缓解部分压力。当系统似乎没有压力时,将推迟收集,以避免频繁的不必要的暂停。

【讨论】:

  • 当另一个程序需要大块内存时怎么办?将大块内存留在不再使用它的程序的所有权下似乎是不负责任的
  • 我的意思是大块内存。对于我自己的测试,我制作了一个大小为 500,000,000 的私有变量 byte[] 的窗口(6 个左右的窗口会导致系统内存不足)垃圾收集器将把该内存保留在我的程序的所有权中,直到我的程序要求更多当我的程序基本上空闲时,这些窗口消失很久之后的内存
  • @Assimilater 虽然您的担忧是绝对正确的,但这就是跟踪 GC 的工作方式。我亲自在 C# 中推出了我自己的数组类,用于直接利用操作系统分配器的超大数组(最多 32 GB),并且必须通过IDisposable 显式选择“释放”(调整为零)界面。
  • 在大型数组的情况下,它只是我的测试代码来帮助我了解 GC 发生了什么。在实践中,大量内存占用来自我项目中的其他来源。已处理的 IO 流(使用 using 语句,而不是显式作为私有变量)、它们早已使用的缓冲区已超出范围、已超出范围的 oxyplot 模型、用于存储这些图的时间和 fft 数据的缓冲区.为所有这些类(模型)添加一个一次性接口来弥补 GC 的这个缺点对我来说似乎有点……
  • 我找到了this gem,稍后再考虑
【解决方案2】:

您不必显式调用GC.Collect

确实,您的代码使用了大量内存,但是垃圾收集有一个智能算法来确定它应该何时运行。它在您内存不足或内存使用量突然增加时运行。只要还有额外的内存,垃圾收集器就不会做任何事情。这大大提高了性能。如果 GC 持续运行,您的性能将很糟糕。

在 .NET 中也不需要将变量设置为 null。运行时将跟踪正在使用的变量,并在必要时将它们标记为收集。

【讨论】:

  • +1 表示关于空值的注释。但是,它不仅会在内存不足时运行,它还会在内存使用量增长超过在进程生命周期内适应的特定水平时运行(例如突然的大量内存增长)。
【解决方案3】:

您不应明确使用GC,而应避免一次读取所有数据。 这是您正在执行的相当糟糕的做法...从文件中读取时,您应该使用using-statement 并在小缓冲区中读取。

using(StreamReader sr = new StreamReader(filename)){
     //while has data
     char[] buffer = new char[64000]; // or whatever you like
     sr.ReadBlock(buffer, 0, buffer.Length);     
     //do your conversion here
}

here 中查找StreamReader.ReadBlock()

【讨论】:

  • 可能是因为它并没有真正解决问题。此外,有时如果无法重组所有数据以适应流模式,则需要一次读取所有数据。仅供参考,我不是反对者。
【解决方案4】:

.NET CLR 自己管理内存,并在必要时调用垃圾收集器。

无论如何,即使您有时间要求严格的代码,您也可以依靠 .NET CLR 在适当的时间调用垃圾收集器。 GC 非常高效,而且释放内存的速度非常快。

所以真的没有问题。不要调用GC,你不需要。这是 .NET。

【讨论】:

    【解决方案5】:

    当您处理如此大的文件和内存结构时,请考虑使用流而不是打开和转换内存中的完整文件。无论如何,您不应该调用 GC collect。

    【讨论】:

      【解决方案6】:

      .NET 垃圾收集器是分代收集器。从代的角度来看,大对象(85K 或更大)属于第 2 代,因为它们仅在存在第 2 代收集时才被收集,包括所有代,这不像第 0 代收集那样经常发生,因为对象大小,大对象通常是数组(在您的情况下是字节 [])。例如,第 2 代收集可能由以下原因引起:

      • 分配超过第 0 代或大对象阈值 大多数 GC 发生是因为托管堆上的分配
      • 系统处于内存不足的情况 当我收到 来自操作系统的高内存通知。
      • 当有人调用 GC.Collect 时调用 System.GC.Collect 第 2 代(通过不向 GC.Collect 传递任何参数或传递 GC.MaxGeneration 作为参数)

      因此,当您的系统需要内存时,它通常会释放字节数组,而无需您通过调用 GC.Collect 来强制它释放它。

      看看this article

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-07-22
        • 2017-01-21
        • 1970-01-01
        • 2016-06-09
        • 1970-01-01
        • 1970-01-01
        • 2021-08-02
        相关资源
        最近更新 更多