填充 MemoryStream 时出现 OutOfMemoryException:16GB 系统上的 256MB 分配

Posted

技术标签:

【中文标题】填充 MemoryStream 时出现 OutOfMemoryException:16GB 系统上的 256MB 分配【英文标题】:OutOfMemoryException while populating MemoryStream: 256MB allocation on 16GB system 【发布时间】:2013-03-13 18:26:59 【问题描述】:

我在我的开发 IIS 服务器(来自 VS2010 IDE)上运行以下方法,在 64 位 Windows 7 机器上安装了 16GB 内存:

public static MemoryStream copyStreamIntoMemoryStream(Stream stream)

    long uiLen = stream.Length;
    byte[] buff = new byte[0x8000];

    int nSz;
    MemoryStream ms = new MemoryStream();
    try
    
        while ((nSz = stream.Read(buff, 0, buff.Length)) != 0)
        
            ms.Write(buff, 0, nSz);
        
    
    finally
    
        Debug.WriteLine("Alloc size=" + ms.Length);
    

    return ms;

我在这一行得到System.OutOfMemoryException

ms.Write(buff, 0, nSz);

分配 268435456 字节时抛出:

分配大小=268435456

这是 0x10000000 或 256 MB。所以我想知道是否需要设置一些全局设置才能使其正常工作?

这是项目配置设置的屏幕截图:

【问题讨论】:

如果您知道MemoryStream constructor 的初始大小大约有多大,您可以尝试设置一个更大的初始大小吗? @JonathonReinhart:刚刚尝试过。 stream.Length 设置为 0。这是一个 ZIP 存档流,所以我猜它没有预先提供。 问题是 MemoryStream 每次超出时都会将其内部缓冲区的大小翻倍。所以也许你可以尝试设置一个 512MB 或 1GB 的初始大小。 乔恩,我尝试设置更大的尺寸,但发现这只是解决了问题:它没有在扩展时死亡,而是在新版本中提前死亡。这真的很令人沮丧,因为在我的项目中它发生在 10 MB,因为我从一个流向另一个流馈送。 这里codeproject.com/Articles/348590/A-replacement-for-MemoryStream和这里memorytributary.codeplex.com的MemoryStream的可能替代品(我自己没试过。) 【参考方案1】:

如果您使用默认的 VS 开发服务器,则您正在 x86/32 位进程中运行代码。如果您使用的是完整的 IIS - 很可能在 IIS 中,特定的 AppPool 被配置为在 x86(32 位模式)下运行,因此地址空间非常有限(2GB,除非您将应用程序标记为大地址感知)。

如果是 IIS,请确保您已将应用轮询配置为运行 x64(不确定默认值是什么)。确保您的代码目标设置为 AnyCPU 或 x64。

对于独立的 C# 应用程序 - 默认情况下,它们使用 x86 或 AnyCPU/Prefer x86 编译 - 将目标平台更改为 x64。

要获得对 IIS 的 x64 支持,您可以install full IIS 或从Download IIS 8.0 Express 安装 IIS Express 8.0(Windows 7 附带的 7.5 仅为 32 位)。

旁注:

如果您刚刚安装了完整的 IIS,请确保更新您的解决方案以将 IIS 用于站点的主机。 我没有机器来检查是否有任何额外的步骤需要使用 IIS Express 的 x64 支持。看看这个问题 - Can't get IIS Express 8 beta to run website as 64-bit process - 因为它可能会给你一些想法。 您也可以尝试根据此处的建议构建自己的 x64 版本的开发服务器 - Is Visual Studio 2010 WebDev WebServer (Cassini) 64-bit compatible?

【讨论】:

我刚刚检查过(见上面的截图),它似乎被编译为“Any CPU”。是 x64 的意思吗? @c00000fd,好的。 MemoryStream 应该稍后会失败(大约从 1GB 增长)......所以很奇怪你这么早就在 64 位进程中遇到错误...... 是的。这就是为什么我把它贴在这里。我可以检查任务管理器以确保它以 x64 运行。它在asp.net开发服务器中运行的是什么进程? 我想是WebDev.WebServer40.EXE,不是吗?如果是这样,它确实作为 32 位进程运行。 哦,哇。谢谢。他们还没有编写 64 位版本,这真是太蹩脚了。我可以在不是服务器版本的 Windows 7 上安装 IIS 吗?【参考方案2】:

简答 - 开发服务器是 32 位进程。

“为什么只有 256Mb?”的长答案

首先,让我们了解它是如何工作的。

MemoryStream 具有内部 byte[] 缓冲区来保存所有数据。它不能预测这个缓冲区的确切大小,所以它只是用一些初始值来初始化它。

Position 和 Length 属性不反映实际缓冲区大小 - 它们是反映写入字节数的逻辑值,很容易小于实际物理缓冲区大小。

当这个内部缓冲区不能容纳所有数据时,应该“重新调整大小”,但在现实生活中,这意味着创建新缓冲区大小是前一个缓冲区的两倍,然后复制数据从旧缓冲区到新缓冲区。

因此,如果您的缓冲区长度为 256Mb,并且您需要写入新数据,这意味着 .Net 需要找到另一个 512Mb 数据块 - 其余所有数据都已到位,因此堆应位于当您收到 OutOfMemory 时,内存分配时至少有 768Mb。

另外请注意,默认情况下,.Net 中的单个对象(包括数组)的大小不能超过 2Gb。

好的,这是模拟正在发生的事情的示例:

        byte[] buf = new byte[32768 - 10];

        for (; ; )
        
            long newSize = (long)buf.Length * 2;
            Console.WriteLine(newSize);

            if (newSize > int.MaxValue)
            
                Console.WriteLine("Now we reach the max 2Gb per single object, stopping");
                break;
            

            var newbuf = new byte[newSize];
            Array.Copy(buf, newbuf, buf.Length);
            buf = newbuf;
        

如果它内置 x64/AnyCPU 并从控制台运行 - 一切正常。

如果它跨 x86 构建 - 它在控制台中失败。

如果你说 Page_Load,内置 x64,并从 VS.Net Web 服务器打开 - 它会失败。

如果您对 IIS 执行相同操作 - 一切正常。

希望这会有所帮助。

【讨论】:

最重要的是,分配的 512 MB 必须是连续的内存块。并不是说 32 位没有足够的可用地址空间;只是地址空间是碎片化的。使用多个较小字节数组的 Stream 实现可能更接近 2 GB 的限制。 感谢您的解释。不过,在另一个主题上——为什么操作系统不能对其内部 RAM 映射进行碎片整理,比如说,当计算机不使用时?因为它不能分配 512MB 的数据太可笑了。什么,是Windows 95吗?有时我想知道微软是否在 Windows 7 中做了什么特别的事情...... 它没有你想象的那么糟糕)例如阅读这个答案***.com/a/5243503/2170171 在大多数情况下,512Mb 将在 16Gb RAM 机器上分配而没有任何问题,这里是这种特定情况***.com/questions/686950/…跨度> 我知道这是旧的,但我想知道 Stream.CopyTo() 方法是否不能解决小于 int.MaxValue 的流长度的内存分配问题?在这种情况下,以下代码可能不太容易出现 OutOfMemory 异常:if (int.MaxValue >= memoryStream.Length) MemoryStream memoryStream = new MemoryStream(); stream.CopyTo(memoryStream); memoryStream.Capacity = (int) memoryStream.Length; // Optional but helps eliminate null padding. @Teorist default CopyTo 实现与所讨论的代码完全相同 - 在缓冲区中读取源并以块的形式写入目标,从而导致内部缓冲区重新分配。你可以看到 Stream 在此处引用源 github.com/Microsoft/referencesource/blob/master/mscorlib/… 为了避免缓冲区重新分配,我们需要 1)能够提前知道源流长度(并非所有流都允许)和 2)在目标中预分配整个内存块一次

以上是关于填充 MemoryStream 时出现 OutOfMemoryException:16GB 系统上的 256MB 分配的主要内容,如果未能解决你的问题,请参考以下文章

尝试填充占位符时出现Tensorflow错误

将 g 711 数据包转换为 wav 文件时出现断断续续的声音

填充数组时出现java.lang.NullPointerException [重复]

Swift:填充二维数组时出现索引超出范围错误

从脚本填充数据库时出现 Django Heroku DataError

解密时出现填充错误,但仍然有效