【问题标题】:C# StreamWriter and StreamReader memory managment problem, why won't memory used deallocate?C# StreamWriter 和 StreamReader 内存管理问题,为什么使用的内存不会释放?
【发布时间】:2011-03-06 22:04:21
【问题描述】:

所以我使用了一个 StreamReader,它使用 MemoryStream 写入 StreamWriter 和此应用程序内部,但内存使用量增加了 300mb(来自较大的输入之一),并且在我完成使用后不会取消分配:

StreamWriter log = new StreamWriter("tempFile.txt");
log.Write(reader.ReadToEnd());
log.Close();

reader.DiscardBufferedData();
reader.Close();
reader.Dispose();
memoryStream.Dispose();
log.Dispose();
GC.Collect();

在此之前和之后,我得到了 RAM 使用量,之前它比之后少 300 mb,但我不知道为什么。考虑到这里唯一发生的事情是来自阅读器的数据被放置在文本文件中,我已经做了我能想到的一切来释放该内存我不明白为什么甚至需要使用任何大量内存暂时地。有什么我遗漏的吗?...谢谢。

【问题讨论】:

  • 我建议在您的应用程序上运行性能分析器。您可能会发现您遇到的性能问题实际上与报告的内存占用无关。根据您写入磁盘的数据量,影响其他进程的更大罪魁祸首可能是磁盘 I/O 资源的争用。内存真的不应该成为性能问题(就对其他进程的影响而言),直到您实际溢出物理 RAM 并导致大量磁盘分页。

标签: c# memory-management streamreader memorystream streamwriter


【解决方案1】:

您是否正在查看进程本身使用的 RAM?我没想到会下降。该进程将保留该内存并重用它以进行进一步的分配。它不会将其交还给操作系统。这正是 .NET 的工作方式。

您可以通过一些 CLR API 调用潜在地让它丢弃内存 - 但通常我不会。

这并不意味着内存真的泄漏了——它仍然可以被同一个进程使用。例如,如果您再次执行相同的操作,它可能不需要增加堆的大小 - 您会看到内存使用率在进程级别保持不变。使用 CLR 性能图查看托管堆中使用的内存,以查看是否存在 真正的泄漏。

(对于应用程序实际使用的内存量,还有各种不同的衡量标准。哪一个有趣取决于您要做什么。)

编辑:如果您的代码 sn-p 真正表示您正在使用的代码,那么还有一个更简单的选择:流式传输数据。

using (TextWriter writer = File.CreateText("tempFile.txt"))
{
    CopyText(reader, writer);
}

static void CopyText(TextReader reader, TextWriter writer)
{
    char[] buffer = new char[8192];
    int charsRead;
    while ((charsRead = reader.Read(buffer, 0, buffer.Length)) > 0)
    {
        writer.Write(buffer, 0, charsRead);
    }
}

请注意,除非您实际更改编码,否则您可以在不使用 TextWriter/TextReader 对开始的情况下执行此操作。

这样您就不需要在内存中保存整个字符串以及它的二进制表示(在MemoryStream 中)。当然,您仍然拥有二进制表示 - 您是否必须将所有内容写入 MemoryStream 开始?

【讨论】:

  • 我正在使用 System.Diagnostics.PerformanceCounter()s NextValue() 来获取可用内存,似乎 RAM 使用率变得非常危险。此应用程序将是一个在后台运行的进程,因此它不会像现在这样占用系统资源,这一点很重要。
  • @Sam 你可以尝试运行另一个使用大量内存的应用程序,看看增加一些内存压力是否会导致使用率下降?因为如果它阻止其他应用程序获取所需的内存,它只会占用资源。
  • 我明白了,我会尝试这样做,但是在运行时,当使用其他应用程序时,即使打开 Windows 开始菜单也会出现明显的减速。
  • @Sam F:我添加了一些代码,通过流式传输数据而不是将其全部读取到一个大字符串中,可以显着减少所需的内存量。
  • 我不确定是否必须将其写入 MemoryStream,这是 SharpSVN 教程中的一个示例,我一定会检查该函数是否为其他类重载。谢谢你的建议! 8192,是字符串的大小吗?我知道系统喜欢大小为 2 的幂,但我只是想知道为什么要专门使用那个大小。再次感谢。
【解决方案2】:

除了 Jon 发布的内容之外,.NET 运行时的行为方式为您提供了一些重要的好处 - 保持为进程分配的内存是一件好事,除非系统内存不足(在这种情况下,运行时可能会释放它)。

例如,如果您经常需要分配大量内存(如您在此处发布的方法),那么如果进程已经分配了内存(即使它不用于存储任何 . NET 对象)。下次运行他的方法,分配会快很多!

无论如何,您可以做的几件事是:

  • 您可以尝试运行另一个内存密集型应用程序,以查看运行时是否在系统需要时释放内存
  • 您可以使用一些 .NET 分析器来查看在运行该方法后是否有任何您希望收集的活动对象

【讨论】:

    【解决方案3】:

    我不确定它是否会有所帮助,但请尝试一些 using 范围界定:

    using (StreamReader reader = new StreamReader(somestream))
    using (StreamWriter log = new StreamWriter("tempFile.txt"))
    {
        log.Write(reader.ReadToEnd());
    }
    

    【讨论】:

      【解决方案4】:

      在上述段中,所有log、reader 和memoryStream 仍在范围内,因此无法进行垃圾回收。我不知道Dispose 在这些对象上的实现是什么,但是即使在调用Dispose 之后,它们很可能仍然在内存中保存大量数据(因为Dispose 通常只关闭文件句柄,非托管内存等,不一定会删除内部缓冲区)。如果您希望它按预期工作,则必须让所有引用的对象超出范围并变为未引用。

      不过,实际上,除非内存使用导致您明显感到痛苦,否则您不必担心这一点。

      【讨论】:

      • 似乎在整个程序运行时的某些时候,操作系统开始蠕动,只是打开窗口并四处移动它们成为一项缓慢且无响应的任务。
      • 当您不在调试器中运行时,可以在最终读取后直接对这些值进行垃圾收集。您甚至可以稍后再写入变量,但这并不能阻止它更早地被 GC。
      • @Jon Skeet:所以整个应用程序使用的 RAM 比我预期的多 500 mb,这 500 mb 对 .NET 来说是自然而然的吗?我认为它保留的缓冲内存量是有限的。
      • 这表明您在某些时候需要这么多内存。我不会特别期望它会恢复记忆。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-18
      • 2015-05-15
      • 2013-01-12
      • 2014-12-27
      相关资源
      最近更新 更多