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

我正在运行一些测试,以查看我的日志记录将如何执行而不是执行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.Exception of type 'System.OutOfMemoryException' was thrown. 即使我的Process Explorer说我有大约700Mb的免费ram ???

这是屏幕截图(以防万一)

Process Explorer 在此处输入图像描述

这是winform

在此处输入图像描述

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

 bw.Write(i); 

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

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

重要的是:

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

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

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

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

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

这可能是由内存碎片引起的。

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

有关详细信息,请参见此处

任何大于85,000字节的对象都将放置在大对象堆上,但是对于阈值仅为1000倍(或8000字节)的双精度数组除外。

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

您不应该使用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

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