本文可帮助你排查 ASP.NET 中的内存 不足错误。
原始产品版本: ASP.NET
原始 KB 数: 2020006
现象
Microsoft客户支持服务中看到的最常见问题之一是 OutOfMemoryException
方案。 因此,我们汇集了一系列资源,以帮助排查和识别内存问题的原因。
在介绍故障排除 OutOfMemoryException
的详细信息之前,请务必了解导致此问题的原因。 与许多开发人员认为的相反,安装的 RAM 量不会影响安装 OutOfMemoryException
的可能性。 32 位作系统可以处理 4 GB 的虚拟地址空间,无论该框中安装的物理内存量如何。 其中,为作系统(内核模式内存)保留 2 GB,并将 2 GB 分配给用户模式进程。 为内核模式内存分配的 2 GB 在所有进程之间共享,但每个进程都获得自己的 2 GB 用户模式地址空间。 这一切都假定你未在启用开关的情况下 /3gb
运行。
当应用程序需要使用内存时,它会保留虚拟地址空间的区块,然后从该区块提交内存。 这正是 .NET Framework 垃圾回收器(GC)在需要内存来增长托管堆时执行的作。 当 GC 需要小型对象堆的新段(其中对象小于 85 KB)时,它将分配 64 MB。 当它需要大型对象堆的新段时,它会分配 16 MB。 必须满足这些大型分配,因为进程必须处理的 2 GB 地址空间的连续块。 如果作系统无法满足 GC 对连续内存块的请求, System.OutOfMemoryException
则会发生 (OOM)。
备注
在 64 位作系统上运行的 32 位进程可以解决 4GB 的用户模式内存问题,在 64 位作系统上运行的 64 位进程可以处理 8TB 的用户模式内存,因此 OOM 在 64 位作系统上不太可能。 可以在 64 位作系统上运行的 32 位进程中体验 OOM,但在进程使用接近 3 GB 的专用字节之前,它通常不会发生。
出现 OOM 条件的原因有两个。
- 进程使用大量内存(通常在 32 位环境中超过 800 MB)。
- 虚拟地址空间碎片化,减少了大型连续分配成功的可能性。
还可以看到 OOM 条件,因为 1 和 2 的组合。 有关详细信息,请参阅 ASP.NET 中的 System.OutOfMemoryExceptions 疑难解答。
出现 OOM 时,你可能会注意到以下一个或多个症状:
应用程序崩溃。 有关详细信息,请参阅谁是这个 OutOfMemory 家伙, 为什么当我留下大量内存时, 他会导致我的进程崩溃?
应用程序可能会遇到任务管理器或性能监视器指示的高内存。
请求可能需要很长时间才能处理。
在 Internet Information Services (IIS) 7 上,可以使用 IIS 7 中的跟踪排查失败的请求来排查长时间运行的请求。
由于 OOM,用户可能会报告应用程序中的错误消息。
在确定 OOM 条件的原因时,你实际上正在努力确定高内存情况或碎片地址空间的原因。 虽然我们无法记录这些情况的所有可能原因,但我们经常看到一些常见原因。
以下信息概述了 OOM 条件的常见原因,以及解决其中每个原因的解决方法。
字符串串联
托管应用程序中的字符串(使用 .NET Framework 编写的应用程序)是不可变的。 将新值分配给字符串时,将创建现有字符串的副本。 新值将分配给新字符串。 它通常不会导致任何问题。 但是,当大量的字符串串联起来时,它最终会导致比开发人员可能意识到的更多的字符串分配。 这可能导致内存增长和 OOM 条件。
若要避免由于字符串串联而出现 OOM,请确保使用类 StringBuilder
。 有关详细信息,请参阅 如何改进 Visual C# 中的字符串串联性能。
托管堆中的碎片
托管应用程序中的垃圾回收器(GC)压缩堆以减少碎片量。 但是,可以在托管应用程序中固定对象。 固定对象无法在堆压缩期间移动。 这样做将更改对象所在的地址。 如果应用程序固定大量对象,并且/或长时间固定对象,则可能会导致托管堆中的碎片。 这可能会导致 GC 更频繁地增长托管堆,并导致 OOM 条件。
由于固定自 .NET Framework 1.0 以来,我们一直在努力最小化 OOM 条件。 每个版本都进行了增量改进。 但是,如果需要固定对象,仍可以实现的设计模式会很有用。
虚拟地址(VA)空间中的碎片
每个进程分配了一定数量的内存,该内存表示进程的 VA 空间。 如果 VA 空间碎片化,则会增加 GC 无法获取大量连续内存来增大托管堆的可能性。 这可能会导致 OOM 条件。
VA 空间中的碎片通常由以下一个或多个方案引起:
将同一程序集加载到多个应用程序域中。
如果需要在同一应用程序池中运行的多个应用程序中使用程序集,请对程序集进行强名称并将其安装到 GAC 中。 这样做可确保程序集只加载到进程中一次。
在生产环境中运行应用程序,并将元素的调试属性
<compilation>
设置为true
.- 元素的
<compilation>
调试属性应在false
生产环境中时。 - 可以使用配置
<deploy retail="true" />
来确保在产品中始终禁用调试。 有关详细信息,请参阅部署元素(ASP.NET 设置架构)。
- 元素的
在 eXtensible 样式表语言(XSL)转换或创建
XmlSerializers
中使用脚本。在这种情况下,由可扩展样式表语言转换(XSLT)脚本
XmlSerializers
或 .
返回大型数据集
使用来自数据库或其他数据源的数据时,必须限制返回的数据量。 例如,缓存返回整个数据库表的查询结果,以避免在需要时从数据库检索部分数据的成本不是很好的方法。 这样做很容易导致内存过高,并导致 OOM 条件。 允许用户启动类似的查询是创建高内存情况的另一种常见方法。 例如,以字母 S 开头,返回位于得克萨斯州的所有公司或所有客户的雇员,姓氏以 S 开头。
始终限制可从数据库返回的数据量。 不允许查询,例如 SELECT * FROM. . .
,因为随后无法控制页面中显示的数据量。
同样重要的是,确保不会在 UI 元素(如 GridView 控件)中显示大量数据。 除了返回的数据所需的内存外,还可以在字符串和 UI 元素中使用大量数据来呈现结果。 通过实现分页和验证输入,以便不返回大量数据集,可以避免此问题。
在启用了跟踪的生产环境中运行
ASP.NET 跟踪是用于对应用程序进行故障排除的强大功能。 但它不应该留在生产环境中。 ASP.NET 跟踪使用数据结构(例如 DataTables
存储跟踪信息),随着时间的推移,它们可能会导致高内存状况,从而导致 OOM。
应在生产环境中禁用跟踪。 为此,可以在 web.config 文件中将enabled
元素的属性<trace>
设置为 false。 通过使用 <deploy retail="true" />
零售部署,还可以在应用程序中禁用跟踪。
泄漏本机资源
许多托管资源也将使用本机资源。 由于 GC 不会清理本机资源,因此开发人员负责实现和调用 Dispose 方法来清理本机资源。 如果使用实现 IDisposable
接口的类型,并且不调用 Dispose
该方法,则可能会泄漏本机资源并导致 OOM 条件。
这些对象应实现 iDisposable
接口,并且当不再需要这些对象时,应对这些对象调用 Dispose
该方法。