原文地址:Who is this OutOfMemory guy and why does he make my process crash when I have plenty of memory left?
发布时间:Friday, November 25, 2005 10:43 AM
作  者:Tess  

为了回答这个问题,我们先来看看一些概念。

工作在32位系统上,可以寻址4GB内存。典型地, 其中2GB留给操作系统,另外2GB则用于各个用户态的进程,如w3wp.exe(asp.net)进程。这块2GB的内存叫做虚拟内存,它和另外附加的RAM是独立的。RAM的大小仅决定了有多少内存分页和交换可用,如,程序多快能访问它们。

一个进程分配内存时,它是这样进行的:首先,预定虚拟内存;然后,提交内存到该块中(这是实际使用的内存)。提交的内存叫做私有字节。

虚拟地址空间用于存放进程中的一些项目,例如:

  • 动态链接库
  • 本地堆(非.net堆)
  • 线程(每个线程保留1MB内存堆栈)
  • .net堆(托管变量)
  • .net装载堆(程序集及相关的结构变量)
  • com组件分配的虚拟内存

虚拟分配的内存并不是次序排列的(或者很少按次序排列)。例如,dll会装载在各自的地址空间,因此dll间会留下内存空隙。另外,重新分配的虚拟内存间也会留下空隙。这就意味着尽管拥有2GB的虚拟内存,你却不可能完全使用它。因为使用了足够多的内存后,这块内存看起来就会像瑞士硬干酪,起码得有足够的空间让你的叉子能叉进去吧?

这就是遇上OutOfMemory异常时发生的东西。

以后我可能会讨论更多关于.net内存管理知识,但现在我将只做简单介绍,因为已经有一些很好的博客对此加以介绍了,如Maoni的CLR性能博客 http://blogs.msdn.com/maoni/ 和http://blogs.msdn.com/yunjin

在.net框架中,垃圾回收器就是内存管理者,它以堆的方式预定虚拟内存。一旦这些堆被分配,就会创建新的.net对象实例。这个对象将被存储在堆内存断中,此时也将内存提交。

在.net框架1.1(服务器版本)中,会创建规则的.net堆, 大小为64MB。(若使用8+处理器或手动改变设置,该堆大小会有所变动。在这里先忽略这些情况,而仅讨论一般的情形)


这些64MB的片断需以一大整块的形式分配,而不能在这里分配32MB,在那里分配32MB。因此这些64MB的内存片断填充完后,就需要在2GB的内存空间中去寻找一块64MB或更大的空闲内存块,用以分配新的内存块。若这样大小的空闲内存块没了,就会发生OutOfMemory异常。

这些OutOfMemory 异常通常不是致命的,但仍然很糟糕,因为它们会使进程处于一种不稳定的状态。然而,当垃圾回收器做内存整理时,它本身的内也需要空间,这样它才能整理内存。且使用的内存越多,也将需要越多的这些内部结构体。若没有留给垃圾回收器足够的空间用于垃圾整理,就会产生一个致命的内存异常,进程也将终结。

由上所知,预定的内存(性能监视器中的虚拟字节)将比提交的内存字节(性能监视器中的私有字节)大得多。通常在内存使用率高的时候,差别大约是500MB.当虚拟字节达到大约1.2-1.4GB的时候,再寻找64MB的空闲块将变得非常困难,这意味着此时一个正常的.net进程将会发生OutOfMemory异常。

再次声明,这个值可能会因应用程序或装载的dll和本地com组件的内存分配模式(等等)不同会有所差别。

现在该知道为什么会出现OutOfMemory 异常了吧?尽管仍然还有许多的RAM可用。下一任务是去找出为什么使用这些内存,我将会花些时间在我以后的博客帖子中加以讨论。