奇怪的情况是,IIS / Windows 2008 R2 中的 .Net OutOfMemoryExceptions 会在被访问的应用程序的随机页面上被抛出。
我们有大约 1000 个独立的站点,它们是相同的 .Net 应用程序(每个站点有不同的代码库文件夹和应用程序池)。64 位 Windows 并运行 .Net 2.0,应用程序使用“AnyCPU”标志进行编译。
由于相同的代码在旧服务器上运行,并且从未抛出内存不足异常,我们暂时不会花费大量时间来分析应用程序、检查转储和执行代码优化,这将有助于避免大型对象堆碎片(因此,我们希望获得一些关于可能的服务器配置问题的提示,这些问题可能是罪魁祸首,而不是查看代码库并对其进行优化……)。
配置 1 - Rackspace CloudSites(共享主机,我们只能通过 FTP 访问,无法访问 IIS 设置):
1 台 IIS 服务器,我们无法控制管理它,但被告知每个应用程序池的回收限制为 250MB。在我们的 1000 个站点中,许多站点(20-50 个左右)显然共享同一个应用程序池。我们从未在这里遇到 OutOfMemoryExceptions,并且多年来一直在其上运行应用程序。
配置 2 - Rackspace 专用服务器(完全控制):
拥有 128GB RAM 的巨型服务器,专用,每个站点都有自己的应用程序池。所有应用程序池都具有相同的设置(350MB 回收限制)。不确定这是否重要,但此服务器上的页面文件大小为 4GB(不知道配置 1 的大小是多少 - 是否需要增加/解决这个问题?)。
两种配置都在 2 或 3 个 Web 服务器之间进行负载平衡,但这本身实际上并不重要,因为我们看到没有流量的站点因 OutOfMemoryExceptions 而被终止。
答案1
在我写这篇文章的时候,它变得越来越严重,以下是要点
总结
- 增加页面文件的大小(我建议至少增加到 40GB,如果你有足够的磁盘容量和 I/O,那么可以增加更多,但请阅读底部的文章)
- 增加
frequentHitThreshold
和frequentHitTimePeriod
值(查看 Web 服务缓存性能计数器并进行相应调整) - 降低最大响应大小值设置为 85KB 或以下,以避免大对象堆中的缓存条目
- 降低应用程序池回收的内存限制,这没有多大意义
- 考虑将具有相同或相似代码库的应用程序分组到应用程序池中
原始答案
不确定这是否重要,但此服务器上的页面文件大小为 4GB(不知道配置 1 有什么 - 是否需要增加/解决这个问题?
这个,^
就在这里,^
看看它。
我敢打赌,这正是您的应用程序OutOfMemoryException
在响应看似最随机和最良性的请求时抛出异常的原因,但要了解原因,让我们先明确一件事:
OutOfMemory
并不意味着你的服务器内存不足!
我知道这听起来像是一个糟糕的笑话,但事实并非如此。抱怨内存耗尽的不是操作系统,而是进程。
如果您不明白最后这句话的含义,请继续阅读。
内存管理 101
当进程从操作系统分配内存时,它会以一系列称为页面,每块 4 千字节,进程可以将其视为自己的(这通常称为虚拟地址空间)。
由于对象(例如字符串、XML 文档、图像或任何需要保存在内存中的内容)可能超过 4KB 的页面大小,因此进程将需要不时从该内存中分配多个连续的页面。
然而,随着时间的推移,即使使用 .NET CLR,内存空间也会变得碎片化。垃圾收集器将尽力帮助您的应用程序通过在收集期间重新排列工作集中的页面来更好地利用地址空间(这实际上与磁盘碎片整理相同),但指向大型对象堆的指针将保持不变。
IIS 7.x 如何发挥作用
正如最近在这个答案中解释,IIS 还将尝试在为您的应用程序提供服务的同一进程中存储尽可能多的可缓存输出对象(例如最大 256KB 的静态文件) - 除了该答案中的建议之外,您还可以尝试使用以下方法调整缓存频率阈值:<serverRuntime>
配置元素。
无论如何,IIS 7.5(在其默认配置下)非常关心为其工作进程分配足够的内存,并且即使在“无流量”的情况下,当工作进程启动时,即使磁盘上的应用程序代码库略小,也经常会看到工作进程占用前 100MB 的内存。
这与页面文件有什么关系?
无需研究生水平的数学知识就能看出,100MB * 1000 个进程与操作系统提供的 128GB RAM 相差无几。尽管 IIS 会尝试为其工作进程分配尽可能多的内存,但它会在某个时候停止,为操作系统留出一些空间,大约占安装总内存的 85%,无论 RAM 有多少兆或千兆字节(我从未见过这样的事实,而是根据对大量具有不同硬件规格的 IIS 安装的第一手经验得出的)。
此时,操作系统可以通过从页面文件分配页面(存储在物理磁盘上的文件中的页面)来帮助释放内存。由于磁盘容量通常很充足,因此分配大块磁盘存储并不是什么大问题,但如果需要为 1000 个进程分配页面内存,并且只允许在 4GB 空间上这样做,那么不久之后,这些进程将无法分配更长的非碎片化内存序列,并且噗!:该过程抛出一个OutOfMemoryException
,它只是意味着它无法在其可访问的虚拟地址空间中找到足够的相邻页面。
它甚至不必是大型对象。它们只需大于运行时可用的最大连续页面数。理论上,如果您尝试将单个字符附加到当前大小超过 2KB 的字符串,则可能会出现 OutOfMemory 异常。
那么应该将页面文件大小设置为多少?
微软对这个问题的回答一直是:“视情况而定”,但至少需要 1 x RAM + 257MB(这是系统能够写入完整内存转储所需的存储量)。
经验法则似乎是大约 1.5-2 x RAM,但同样,这取决于具体情况,并且已经发表了许多关于如何在给定系统上确定正确的最小和最大页面文件大小的文章。我在底部附上了最相关的文章。
确保监视包含页面文件的磁盘,如果磁盘队列长度计数器开始激增,您可能需要将其移动到专用磁盘,或将其分散到多个磁盘上。
答案2
您可以禁用应用程序池内存限制(将其设置为 0),以快速允许应用程序池占用所需的内存。但是,听起来像是有东西在泄漏,最终会占用所有可用内存(并再次回收应用程序池)。
检查c:\windows\system32\LogFiles\HTTPERR
日志文件中是否存在致命错误消息。
您不必修改 PageFile 设置,该设置仅在 128GB RAM 耗尽时使用。
要追踪内存泄漏的源头,您需要使用 Trace Logs 或 DebugDiag 进行更多挖掘(这不是最简单的任务):
答案3
通过将 1000 多个应用程序池切换为 32 位模式(而不是默认的 64 位),我们的问题得到了解决。我们可以通过切换回 64 位并立即看到内存不足异常来确认这一点。
我认为 Jessen 关于页面文件的观点确实有分量...由于增加页面文件需要重新启动系统,我们尝试切换到 32 位模式应用程序池(不需要重新启动)并且很幸运。
如果有人对此有一些有趣的评论,请随意发表!
谢谢!