排查 IIS 7.x 应用程序池中的本机内存泄漏问题

适用于: Internet Information Services 7.0 及更高版本

本文可帮助你确定 IIS 应用程序池中本机内存泄漏的原因。

概述

请务必记住,在 Web 应用程序为请求提供服务时,高内存分配是正常的。

当 IIS 应用程序池中发生内存泄漏时,增加物理内存 (RAM) 无效,因为此方案中的内存不是物理内存 (RAM) 而是虚拟内存。 下表汇总了来自 Web 应用程序的可寻址内存的虚拟内存。

流程 Windows 具有大型地址感知进程) 的可寻址内存 ( 虚拟字节的实际限制 专用字节的实际限制
32 位 32 位 2 GB 1400 MB 800 MB
32 位 具有 /3GB 的 32 位 3 GB 2400 MB 1800 MB
32 位 64 位 4 GB 3400 MB 2800 MB
64 位 64 位 8 TB 不适用 不适用

应用场景

你始终会看到 Process\Private Bytes 和 Process\Virtual Bytes 都在增加,或者 Process\Private Bytes 和 Process\Working Set of w3wp.exe 正在增加,并且内存\可用字节数正在减少。

它可能会导致 IIS 中的应用程序池出现内存不足异常。

若要恢复,必须重启应用程序池,但执行此操作后,内存使用量再次攀升至较高水平。 看到上述错误时,应用程序池已自动重启。

数据收集

遇到内存使用率过高时,应首先确定 IIS 中应用程序池上工作进程的内存是否泄漏。 可以使用 性能监视器。 有关使用性能监视器的详细信息,请参阅分析性能数据

提示

如果需要确定哪个应用程序池与特定的 w3wp.exe 进程相关联,请打开管理命令提示符,切换到 %windir%\System32\inetsrv 文件夹 (cd %windir%\System32\inetsrv) 并运行 appcmd list wp。 这将在引号中显示w3wp.exe进程的进程标识符 (PID ) 。 可以将该 PID 与任务管理器中提供的 PID 匹配。

确认 w3wp.exe 进程内存使用率过高后,需要提供两条信息,以确定导致问题的原因。

  • 性能监视器数据收集器集。
  • w3wp.exe 进程的用户模式内存转储。

这两个都需要从内存使用率低(例如启动进程)收集,直到内存使用率过高(例如遇到内存不足异常)。

收集性能监视器数据收集器集

性能监视器 (Perfmon) 数据可以实时查看,也可以在数据收集器集中收集,以便稍后查看。 若要排查内存使用率过高的问题,需要收集数据收集器集。 若要创建数据收集器集以排查内存过高问题,请执行以下步骤:

  1. Windows 控制面板打开管理工具
  2. 双击性能监视器
  3. 展开“ 数据收集器集” 节点。
  4. 右键单击“ 用户定义 ”,然后选择“ 新建>数据收集器集”。
  5. 输入 “高内存 ”作为数据收集器集的名称。
  6. 选择“ 手动创建 (高级) ”单选按钮。
  7. 选择“下一步”。
  8. 选择“ 创建数据日志 ”单选按钮。
  9. 选中“性能计数器 ”复选框。
  10. 选择“下一步”。
  11. 选择“添加”按钮。
  12. 从计数器列表中展开 “进程 ”。
  13. Thread 对象中选择专用字节虚拟字节工作集
  14. 从实例>列表中选择“<所有实例”。
  15. 选择“添加”。
  16. 从计数器列表中展开 “内存 ”。
  17. Thread 对象中选择“可用字节数”。
  18. 选择“添加”。
  19. 选择“确定”。
  20. “采样间隔 ”设置为 “1 秒 ”,然后将 “下一步 ”和“完成”。

现在,对话应如下所示:

“数据收集器 01 属性”对话框的屏幕截图。“性能计数器”选项卡处于打开状态。

创建调试诊断 1.2 规则

发生高内存情况时,收集用户模式进程转储的最简单方法是使用调试诊断 (DebugDiag.) 可以下载 调试诊断工具 v2 Update 3

在服务器上安装 DebugDiag 并运行它。 (安装后,你将在 “开始 ”菜单上找到它。)

要确定哪个函数导致内存泄漏,最重要的信息是堆分配的堆栈跟踪。 默认情况下,不会获取这些堆栈跟踪。 可以按进程启用此功能。 使用以下命令启用堆栈跟踪:

gflags -i w3wp.exe +ust

命令不会为已在运行的进程启用堆栈跟踪。 因此,对于无法重启 (例如服务、lsass、winlogon) 的进程,必须重启测试计算机。

使用以下命令验证已为特定进程设置了哪些设置:

gflags -i w3wp.exe

运行 DebugDiag 时,将显示 “选择规则类型 ”对话框。 按照以下步骤为应用程序池创建泄漏规则:

  1. 选择 “本机 (non-.NET) 内存>句柄泄漏>下一步”。

  2. 选择一个进程,然后选择“ 下一步”。

  3. 选择“配置”。

  4. 设置规则,如下图所示:

    可以根据需要调整这些值,但请注意不要指定少量的 MB 以生成大量转储文件。 当专用字节达到 800 MB 时,生成用户转储,此后每个额外的 100 MB。 当虚拟字节达到 1024 MB 时,生成用户转储,此后再增加 200 MB。

    “配置泄露规则的用户转储”对话框的屏幕截图。选中所有选项。

  5. 选择 “保存 & 关闭”。

  6. 选择“下一步”。

  7. 如果需要,请输入规则的名称,并记下转储的保存位置。 如果需要,可以更改此位置。

  8. 选择“下一步”。

  9. 选择“立即>激活规则完成”。

如果即使在获取内存转储时也遇到内存不足错误,则可以手动获取内存转储。

  1. 选择“ 处理 ”选项卡。
  2. 右键单击目标进程。
  3. 选择“ 创建完整用户转储”。

数据分析

出现内存不足错误或创建内存转储后,需要查看两组数据:Perfmon 数据收集器集和内存转储。 让我们首先查看 Perfmon 数据。

分析性能数据

若要查看问题的 Perfmon 数据,请右键单击“用户定义”节点下列出的“高内存”数据收集器集,然后选择“最新报告”。 可以看到类似于下图的内容:

高内存数据收集器集的性能数据的屏幕截图。

若要了解导致 CPU 使用率过高问题的根本原因,请查看使用 DebugDiag 创建的转储。

使用 DebugDiag 进行转储分析

DebugDiag 能够通过执行自动转储分析来识别许多问题。 对于此特定问题,DebugDiag 的性能分析器非常适合帮助确定 CPU 使用率过高问题的根本原因。 若要使用分析器,请执行以下步骤:

  1. 在 DebugDiag 中选择“ 高级分析 ”选项卡。
  2. 选择“ 内存压力分析器”。 请确保使用 MemoryAnalysis.asp 而不是 DotNetMemoryAnalysis-BETA.asp
  3. 选择 “添加数据文件”。
  4. 浏览到创建转储的位置。 默认情况下,这是 C:\Program Files\DebugDiag\Logs 文件夹的子文件夹。
  5. 选择其中一个转储,然后选择 Ctrl+A 以选择该文件夹中的所有转储。
  6. 选择 “打开”
  7. 选择“ 开始分析”。

DebugDiag 需要几分钟时间来分析转储并提供分析。 完成分析后,会看到类似于下图的页面:

DebugDiag 分析报表的屏幕截图。

注意

报表的顶部指示检测到内存泄漏。 在目录中,你将看到一个指向泄漏分析报告详细信息的链接。 选择该链接,你将看到有关按分配计数或分配大小划分的前 10 个模块的信息。 下面是示例:

有关高内存模块的详细信息的屏幕截图。

从此分析中,可以看到 leakcom 组件正在运行。 若要进一步查看 leakcom 的 模块信息 ,如下所示,可以看到该方法 CFoo::crtheap 分配了未完成的内存。

模块中 leakcom 的详细信息的屏幕截图。

下一步是查看方法的代码 CFoo::crtheap 。 执行此操作时,会发现以下代码片段:

STDMETHODIMP CFoo::crtheap(void)
{
    malloc(1024 * 10);

    return S_OK;
}

上述代码肯定会导致内存泄漏,因为分配的内存不会释放。

提示

如果启用堆栈跟踪 (gflags -i w3wp.exe +ust) ,则可以通过使用 WinDBG 分析转储来查看以下调用堆栈。 如果默认禁用堆栈跟踪,则永远不会看到以下调用堆栈。

0:000> !heap -p -a 42ae5b28
    address 42ae5b28 found in
    _HEAP @ 6690000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        42ae5b28 0541 0000  [00]   42ae5b40    02800 - (busy)
        77e9df42 ntdll!RtlAllocateHeap+0x00000274
        75133db8 msvcr90!malloc+0x00000079
        513f3cc7 LeakTrack!CCRTMemoryLT::R90mallocDetour+0x00000067
        75933cef oleaut32!CTypeInfo2::Invoke+0x0000023f
        61f527b8 leakcom!ATL::IDispatchImpl::Invoke+0x00000058
        f05cb4d vbscript!IDispatchInvoke+0x00000059
        f053f40 vbscript!InvokeDispatch+0x000001a5
        f060795 vbscript!InvokeByName+0x00000043
        f06080d vbscript!CScriptRuntime::RunNoEH+0x000022cf
        f054122 vbscript!CScriptRuntime::Run+0x00000064
        f054099 vbscript!CScriptEntryPoint::Call+0x00000074
        f054279 vbscript!CSession::Execute+0x000000c8
        f0544c0 vbscript!COleScript::ExecutePendingScripts+0x00000146
        f052013 vbscript!COleScript::SetScriptState+0x0000014d
        513023c0 asp!CActiveScriptEngine::TryCall+0x00000019
        513027b3 asp!CActiveScriptEngine::Call+0x000000e7
        513022c7 asp!CallScriptFunctionOfEngine+0x0000003e
        513063d5 asp!ExecuteRequest+0x0000014a
        51302676 asp!Execute+0x000001c4
        513028f2 asp!CHitObj::ViperAsyncCallback+0x000003fc
        51302030 asp!CViperAsyncRequest::OnCall+0x0000006a
        563de19 comsvcs!CSTAActivityWork::STAActivityWorkHelper+0x00000032
        771304fb ole32!EnterForCallback+0x000000f4
        771284c7 ole32!SwitchForCallback+0x000001a8
        77126964 ole32!PerformCallback+0x000000a3
        7713df32 ole32!CObjectContext::InternalContextCallback+0x0000015b
        771f47ef ole32!CObjectContext::DoCallback+0x0000001c
        563dfbd comsvcs!CSTAActivityWork::DoWork+0x0000012f
        563e51b comsvcs!CSTAThread::DoWork+0x00000018
        563f24d comsvcs!CSTAThread::ProcessQueueWork+0x00000037
        563f4c0 comsvcs!CSTAThread::WorkerLoop+0x00000135
        773a1287 msvcrt!_endthreadex+0x00000044

总结

通过使用 Perfmon 和 DebugDiag,可以轻松收集有助于确定应用程序池中内存泄漏的原因的数据。 如果使用这些技术找不到根本原因,可以向 Microsoft 开具支持票证。 请务必在支持票证中包含 Perfmon 数据和堆栈跟踪转储,以帮助缩短周转时间。

其他资源