FileStream.Write()抛出OutOfMemoryException的可能原因是什么?

3

我有10个线程在随机位置向一个巨大的文件中写入数千个小缓冲区(每个缓冲区16-30字节)。一些线程在 FileStream.Write() 操作时抛出 OutOfMemoryException 异常。

是什么导致了 OutOfMemoryException?需要注意什么?

我使用的 FileStream 如下所示(对于每个写入的项 - 这段代码从 10 个不同的线程运行):

using (FileStream fs = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite, BigBufferSizeInBytes, FileOptions.SequentialScan))
{
 ...
 fs.Write();
}

我怀疑FileStream内部分配的所有缓冲区在垃圾回收器(GC)释放时没有及时释放。但我不明白的是,为什么CLR不运行一次GC循环并释放所有未使用的缓冲区,而是抛出异常?

5个回答

2
如果您的代码显示有十个线程正在打开文件,则任何时候都会有最多十个未处理的FileStream对象。是的,FileStream确实有一个内部缓冲区,其大小由您在代码中指定的“BigBufferSizeInBytes”确定。请问您可以透露确切的值吗?如果足够大(例如~100MB),那么它很可能是问题的源头。
默认情况下(即在构造时不指定数字),此缓冲区为4kB,对于大多数应用程序通常足够了。一般来说,如果您真的关心磁盘写入性能,那么您可以将其增加到几百KB,但不要超过这个范围。
然而,对于您特定的应用程序来说,这样做并没有太多意义,因为所述缓冲区在Dispose() FileStream对象之前将永远不会包含超过16-30个字节。
回答您的问题,只有在GC运行后无法分配请求的内存时才会引发OutOfMemoryException。同样,如果缓冲区真的很大,那么系统可能还有很多剩余的内存,只是没有连续的块。这是因为大对象堆永远不会被压缩。

缓冲区大小为1MB - 不足以消耗内存。 - ripper234
1
好的,那么我建议您发布一个最小但完整的程序,展示您所描述的行为。我知道这可能需要很多工作,但目前我没有看到您的代码应该有任何内存问题的固有原因。 - user49572
1
一个可以完美解释你所看到的效果的原因是FileStream对象由于某些原因无法被垃圾回收:你是否将FileStream对象添加到某个静态数据结构(例如列表)中?你是否连接了任何事件处理程序? - user49572

1

我已经多次提醒人们注意大对象堆,当你似乎有足够的可用内存或应用程序正常运行时,它可能会相当微妙地抛出异常。

在做几乎完全与您描述的内容相同的事情时,我也经常遇到这个问题。

为了正确回答这个问题,您需要发布更多的代码。但是,我猜这也可能与潜在的万圣节问题(Spooky Dooky)有关。

您从中读取的缓冲区也可能是问题(再次与大对象堆相关),因此您需要在循环中提供更多有关正在发生的情况的详细信息。我刚刚解决了最后一个几乎相同的错误(我正在执行许多并行哈希更新,所有这些更新都需要在读取输入文件时保持独立状态)....

哦!我刚刚滚动并注意到“BigBufferSizeInBytes”,我又倾向于使用大对象堆...

如果我是你(由于缺乏上下文,这非常困难),我会提供一个小的调度“mbuf”,在其中复制和传输数据,而不是让所有不同的线程单独读取你的大型后备数组...(即很难避免使用非常微妙的代码语法导致意外分配)。

0

我也遇到了类似的问题,不知道你最终找到问题的根源了吗?

我的代码在文件之间进行大量复制,传递了相当多的字节文件。我注意到,在进程内存使用保持在合理范围内的同时,系统内存分配在复制过程中过度增加 - 远远超出我的进程使用的内存。

我已经追踪到问题,就是FileStream.Write()调用 - 当删除这行代码后,内存使用似乎符合预期。我的BigBufferSizeInBytes是默认值(4k),我找不到任何可能会导致内存使用增加的地方...

如果你在解决问题时发现了任何有用的信息,请一定告诉我!


这更适合作为评论,而不是问题 :) 我还没有找到一个好的解释来解决这个问题,很抱歉。 - ripper234

0

通常情况下,缓冲区不会在FileStream内部分配。也许问题出在“写入成千上万个小缓冲区”的那一行 - 你真的是这个意思吗?通常情况下,你会多次重复使用一个缓冲区(即在不同的Read/Write调用中)。

此外 - 这是单个文件吗?单个FileStream不能保证线程安全...所以如果你没有进行同步,就应该预料到混乱。


我现在不寻求最优设计,我只想知道为什么会出现OutOfMemoryExceptions。是的,我有许多未被重复使用的小缓冲区(这是在集成测试的背景下)。我为每个这样的缓冲区分配一个FileStream,因此没有线程问题。 - ripper234

0

这些限制可能是由底层操作系统引起的,.NET Framework 无法克服这些限制。

从您的代码示例中我无法推断出您是否同时打开了很多这些 FileStream 对象,或者按顺序快速打开它们。您使用 'using' 关键字将确保在 fs.Write() 调用后关闭文件。关闭文件不需要进行 GC 循环。

FileStream 类真正面向的是对文件的顺序读/写访问。如果您需要快速写入大文件中的随机位置,您可能需要考虑使用虚拟文件映射。

更新:似乎直到 .NET 4.0,虚拟文件映射才会得到官方支持。您可能需要查看第三方实现来获取此功能。

Dave


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接