为啥我的 C# 应用程序中出现内存不足异常?

Posted

技术标签:

【中文标题】为啥我的 C# 应用程序中出现内存不足异常?【英文标题】:Why am I getting an Out Of Memory Exception in my C# application?为什么我的 C# 应用程序中出现内存不足异常? 【发布时间】:2010-10-10 11:54:13 【问题描述】:

我的内存是 4G 物理内存,但是为什么即使我只创建 1.5G 内存对象,我也会出现内存不足异常。任何想法为什么? (我同时看到,在任务管理器的性能选项卡中,内存未满,我也可以在这里输入--所以内存实际上并不低,所以我想我遇到了其他一些内存限制)?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TestBigMemoryv1

    class MemoryHolderFoo
    
        static Random seed = new Random();
        public Int32 holder1;
        public Int32 holder2;
        public Int64 holder3;

        public MemoryHolderFoo()
        
            // prevent from optimized out
            holder1 = (Int32)seed.NextDouble();
            holder2 = (Int32)seed.NextDouble();
            holder3 = (Int64)seed.NextDouble();
        
    

    class Program
    
        static int MemoryThreshold = 1500; //M
        static void Main(string[] args)
        
            int persize = 16;
            int number = MemoryThreshold * 1000 * 1000/ persize;
            MemoryHolderFoo[] pool = new MemoryHolderFoo[number];
            for (int i = 0; i < number; i++)
            
                pool[i] = new MemoryHolderFoo();
                if (i % 10000 == 0)
                
                    Console.Write(".");
                
            

            return;
        
    

【问题讨论】:

添加 cmets 解决您的额外问题...抱歉耽搁了;我在飞机上...... 你真的很酷,马克! :-) 你为什么使用Int32?为什么不int 【参考方案1】:

在普通的 32 位 Windows 应用程序中,进程只有 2GB 的可寻址内存。这与可用的物理内存量无关。

所以 2GB 可用,但 1.5 是您可以分配的最大值。关键是您的代码不是进程中运行的唯一代码。其他 0.5 GB 可能是 CLR 加上过程中的碎片。

更新:在 .Net 4.5 的 64 位进程中,如果启用 gcAllowVeryLargeObjects 设置,您可以拥有大型数组:

在 64 位平台上,启用总大小大于 2 GB 的数组。 数组中元素的最大数量是 UInt32.MaxValue。

<configuration>
  <runtime>
    <gcAllowVeryLargeObjects enabled="true" />
  </runtime>
</configuration>

【讨论】:

.5GB 也可以是机器上运行的所有其他非操作系统组件。 不,每个进程都有一个完全可寻址的 2 GB 虚拟内存空间。 尼克是正确的。每个进程的地址空间都相互独立。除非他们选择涉足共享内存。 1.嗨 JaredPar,1.5G 内存限制是针对每个进程还是每个线程? 2. 您有任何关于此限制的文件吗? :-) 我自己做了更多的研究。我之所以在32位系统有限制是因为应用程序使用虚拟地址访问内存,即使我们可以拥有4G以上的物理内存,但实际root限制了虚拟内存地址空间,对吗?【参考方案2】:

只是对其他点的补充;如果您想访问大量内存,请考虑 x64 - 但请注意最大 single 对象大小仍为 2GB。由于 x64 中的引用更大,这意味着您实际上获得了 smaller 引用类型的最大数组/列表大小。当然,当你达到这个限制时,你可能还是做错了!

其他选项:

使用文件 使用数据库

(与进程内内存相比,显然两者都有性能差异)


更新:在 .NET 4.5 之前的版本中,最大对象大小为 2GB。从 4.5 开始,如果启用了 gcAllowVeryLargeObjects,您可以分配更大的对象。请注意,string 的限制不受影响,但“数组”也应涵盖“列表”,因为列表由数组支持。

【讨论】:

当您说最大单个对象时,您是在谈论 CLR 对象或原始分配大小(本机或托管)。我假设前者但想检查一下。你也有这个参考,我还没有看到。无法想象为什么单个对象要超过 2GB。 1. “注意最大单个对象大小仍然是 2GB”——Marc,你有文件证明这个说法吗?我对单个对象的含义特别感兴趣,因为我们可以组合对象以形成新对象,那么在您的上下文中什么是单个对象? 1.你如何得出结论——“这意味着你实际上得到了一个较小的引用类型的最大数组/列表大小。”来自“因为 x64 中的引用更大”?能否提供更多细节? 我自己做了更多的研究。我之所以在32位系统有限制是因为应用程序使用虚拟地址访问内存,即使我们可以拥有4G以上的物理内存,但实际root限制了虚拟内存地址空间,对吗? Windows 本身对 win32 施加了每个进程 2gb/3gb 的限制。 32 位引用的理论限制为 4gb。 Win64 打破了这两个限制。【参考方案3】:

补充一下之前的回复:您可以超越使用 /3Gb [和可选的 userva] 引导标志引导的系统的 2Gb 限制。

【讨论】:

虽然要使用 /3Gb 开关,但您必须修改可执行文件以在其中手动设置一个标志,以便它能够利用启动标志。 我自己做了更多的研究。我之所以在32位系统有限制是因为应用程序使用虚拟地址访问内存,即使我们可以拥有4G以上的物理内存,但实际root限制了虚拟内存地址空间,对吗?【参考方案4】:

检查您构建的是 64 位进程,而不是 32 位进程,这是 Visual Studio 的默认编译模式。为此,请右键单击您的项目,属性 -> 构建 -> 平台目标:x64。与任何 32 位进程一样,以 32 位编译的 Visual Studio 应用程序的虚拟内存限制为 2GB。

每个进程都有自己的虚拟内存,称为地址空间,它将执行的代码和操作的数据映射到其中。 32 位进程使用 32 位虚拟内存地址指针,这为 32 位进程可以寻址的虚拟内存量创建了 4GB (2^32) 的绝对上限。但是,操作系统需要它的一半(引用自己的代码和数据),为每个进程创建了 2GB 的限制。如果您的 32 位应用程序尝试使用超过其全部 2GB 的地址空间,它将返回“System.OutOfMemory”,即使您计算机的物理内存未满。

64 位进程没有此限制,因为它们使用 64 位指针,因此它们的理论最大地址空间为 16 艾字节 (2^64)。实际上,Windows x64 将进程的虚拟内存限制为 8TB。那么内存限制问题的解决方案就是编译成64位。

但是,默认情况下,Visual Studio 中对象的大小仍限制为 2GB。您将能够创建多个组合大小大于 2GB 的数组,但默认情况下您不能创建大于 2GB 的数组。希望如果您仍想创建大于 2GB 的数组,可以通过将以下代码添加到 app.config 文件来实现:

<configuration>
  <runtime>
    <gcAllowVeryLargeObjects enabled="true" />
  </runtime>
</configuration>

【讨论】:

+1 用于 Visual Studio 构建属性 - 这允许我的应用程序使用它需要的 4GB 多一点。【参考方案5】:

正如其他海报所提到的,作为 32 位应用程序,您拥有最大 2Gb 的可寻址内存。不要忘记开销。您正在创建一个包含 9300 万个对象的数组 - 如果每个对象恰好有 4 个字节的开销,那就是额外的 350Mb 内存。

【讨论】:

我自己做了更多的研究。我之所以在32位系统有限制是因为应用程序使用虚拟地址访问内存,即使我们可以拥有4G以上的物理内存,但实际root限制了虚拟内存地址空间,对吗? 是的,差不多。您所有的指针都存储在 4 个字节中,这设置了它们可以看到多少的限制。在 16 位指针时代,情况更糟。不要问我段:偏移或窗口高内存​​...【参考方案6】:

还有一点需要注意;一些 .NET 对象需要“连续”内存。即,如果您尝试分配一个大型数组,系统可能不仅需要在您的进程中有足够的空闲内存,而且还需要所有空闲内存都在一个大块中......不幸的是,进程内存会随着时间的推移而碎片化,所以这可能不可用。

有些对象/数据类型有这个要求,有些没有……我不记得哪些有,但我记得 StringBuilder 和 MemoryStream 有不同的要求。

【讨论】:

【参考方案7】:

在 32 位 Windows 操作系统上,单个应用程序可以访问的最大“用户模式”内存为 2GB...假设您有 4GB 内存。

Unmanaged VC++ Application's memory consumption on windows server

http://blogs.technet.com/markrussinovich/archive/2008/07/21/3092070.aspx

(你问这个很有趣,因为我昨天问了几乎同样的事情......)

【讨论】:

你好 uzbones,是不是说4G内存没用了?我的应用最多只能消耗 2G? 嗯...不,通过拥有超过 4G 的内存,您可以运行两个程序副本,每个副本消耗 2G 内存。正如 KristoferA 在下面进一步提到的那样,可以进行系统切换以将数量更改为 3G,或者您需要转到 64 位。 我自己做了更多的研究。我之所以在32位系统有限制是因为应用程序使用虚拟地址访问内存,即使我们可以拥有4G以上的物理内存,但实际root限制了虚拟内存地址空间,对吗? 是的,在 32 位系统中,为了访问超过 4G 的内存(2G 用户模式和 2G 系统),操作系统需要使用大于 32 位 int 的索引作为索引.您可以使用 AppDomains en.csharp-online.net/… 解决此问题

以上是关于为啥我的 C# 应用程序中出现内存不足异常?的主要内容,如果未能解决你的问题,请参考以下文章

为啥在比较列表项时出现内存不足异常?

在C#中图像识别的时候出现内存不足的原因

J2me 应用程序在多个时间文件播放时出现内存不足异常

C#自定义异常就这么简单

为啥我的 Spring Boot 应用程序出现异常?

虚拟大小导致程序内存不足