为什么即使有大约700Mb的RAM空闲,我也会得到System.OutOfMemoryException?

时间:2013-03-28 10:49:28

标签: c# winforms out-of-memory

我正在运行一些测试,看看我的日志记录将如何执行而不是File.AppendAllText我首先写入内存流然后复制到文件。所以,只是为了看看内存操作有多快,我就这样做了。

private void button1_Click(object sender, EventArgs e)
    {
        using (var memFile = new System.IO.MemoryStream())
        {
            using (var bw = new System.IO.BinaryWriter(memFile))
            {
                for (int i = 0; i < Int32.MaxValue; i++)
                {
                    bw.Write(i.ToString() + Environment.NewLine);
                }
                bw.Flush();
            }
            memFile.CopyTo(new System.IO.FileStream(System.IO.Path.Combine("C", "memWriteWithBinaryTest.log"), System.IO.FileMode.OpenOrCreate));
        }
    }

i到达25413324时我得到Exception of type 'System.OutOfMemoryException' was thrown.即使我的Process Explorer说我有大约700Mb的免费公羊???

以下是截屏(以防万一)

Process Explorer enter image description here

这是winform

enter image description here

编辑:为了在堆上创建更多对象,我将bw.write重写为此

bw.Write(i);

3 个答案:

答案 0 :(得分:9)

首先,由于您在MemoryStream中累积数据而不是直接将其写入FileStream,因此内存不足。直接使用FileStream,您根本不需要太多RAM(但您必须保持文件打开)。

未使用的物理内存量与此异常没有直接关系,可能听起来很奇怪。

重要的是:

  • 您在进程'虚拟地址空间中有可用的连续内存块
  • 系统提交不超过总RAM大小+页面文件大小

当你要求Windows内存管理器为你分配一些内存时,它需要检查可用多少,但是承诺可以提供多少内存每一个过程。这样的承诺是通过提交来完成的。要提交某些内存意味着内存管理器为您提供了保证,当您最终使用它时可用。

因此,可能是物理RAM已经完全耗尽,但您的分配请求仍然成功。为什么?因为页面文件中有很多可用空间。当你真正开始使用RAM时,通过这样的分配,内存管理器只会简单地分页。所以0物理RAM!=分配将失败。

反之亦然;尽管有一些未使用的物理RAM,分配仍会失败。您的进程通过所谓的虚拟地址空间查看内存。当您的进程在地址0x12340000读取内存时,这是一个虚拟地址。它可能映射到0x786500000x000000AB12340000的RAM(在64位操作系统上运行32位进程),它可能指向仅存在于页面文件中的内容,或者它可能甚至根本没有任何意义。

如果要分配具有连续地址的内存块,则在此虚拟地址空间中RAM需要是连续的。对于32位进程,您只能获得2GB或3GB的可用地址空间,因此尽管存在可用的物理RAM和足够的空间,但是使用它并不太难以存在足够大小的连续块。总未使用的虚拟地址空间。

答案 1 :(得分:3)

这可能是由内存碎片造成的。

大对象进入大对象堆并且它们不会被移动以为事物腾出空间。如果您在可用内存中存在间隙,这可能会导致碎片,当您尝试分配大于任何可用内存块的对象时,这可能会导致内存不足。

See here for more details

任何大于85,000字节的对象都将被放置在大对象堆上,除了双精度数组,其阈值只有1000倍(或8000字节)。

另请注意,32位.Net程序限制为每个对象最多2GB,总体上略低于4GB(可能低至3GB,具体取决于操作系统)。

答案 2 :(得分:0)

您不应该使用BinaryWriter将文本写入文件。请改用TextWriter

现在您正在使用:

for (int i = 0; i < Int32.MaxValue; i++)

这将写入每个写入至少3个字节(数字表示和换行符)。时间是Int32.MaxValue并且您需要至少6GB的内存已经看到您正在将其写入MemoryStream

进一步查看代码,您将以任何方式将MemoryStream写入文件。所以你可以简单地做到以下几点:

for (int i = 0; i < int.MaxValue; i++)
{
  File.AppendAllText("filename.log", i.ToString() + Environment.Newline);
}

或写一个开放的TextWriter

TextWriter writer = File.AppendText("filename.log");

for (int i = 0; i < int.MaxValue; i++)
{
  writer.WriteLine(i);
}

如果你想要一些内存缓冲区,哪个IMO对于日志记录是一个坏主意,因为你会在崩溃期间丢失最后一点写入,你可以使用以下内容创建TextWriter

StreamWriter(string path, bool append, Encoding encoding, int bufferSize)

并传递bufferSize的'biggish'号码。默认值为1024

要回答这个问题,由于MemoryStream调整大小会导致内存不足,并且在某些时候它会变大以适应内存(在另一个答案中已经讨论过)。