适用于: Internet Information Services 7.0 及更高版本
本文可帮助你确定 IIS 应用程序池中本机内存泄漏的原因。
概述
请务必记住,作为 Web 应用程序为请求提供服务时,高内存分配是正常的。
当 IIS 应用程序池中的内存泄漏发生时,增加物理内存(RAM)无效,因为此方案中的内存不是物理内存(RAM),而是虚拟内存。 下表汇总了 Web 应用程序中可寻址内存的虚拟内存。
处理 | Windows | 可寻址内存(具有大型地址感知进程) | 虚拟字节的实际限制 | 专用字节的实际限制 |
---|---|---|---|---|
32 位 | 32 位 | 2 GB | 1,400 MB | 800 MB |
32 位 | 32 位(启用/3GB 开关) | 3 GB | 2,400 MB | 1,800 MB |
32 位 | 64 位 | 4 GB | 3,400 MB | 2,800 MB |
64 位 | 64 位 | 8 TB | 不适用 | 不适用 |
场景
你一直看到 Process\Private Bytes 和 Process\Virtual Bytes 正在增加,或者 Process\Private Bytes and 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) 数据可以实时查看,也可以在数据收集器集中收集以便以后查看。 若要排查内存过高的问题,我们需要收集数据收集器集。 若要创建用于排查高内存问题的数据收集器集,请执行以下步骤:
- 从 Windows 控制面板打开管理工具。
- 双击性能监视器。
- 展开“数据收集器集”节点。
- 右键单击 “用户定义的 ”并选择“ 新建>数据收集器集”。
- 输入 高内存 作为数据收集器集的名称。
- 选择“手动创建”(高级)单选按钮。
- 选择下一步。
- 选择“创建数据日志”单选按钮。
- 选中“性能计数器 ”复选框。
- 选择下一步。
- 选择“添加”按钮。
- 从计数器列表中展开 Process 。
- 从 Thread 对象中选择专用字节、虚拟字节和工作集。
- 从实例列表中选择“所有实例”。<>
- 选择 添加 。
- 从计数器列表中展开 “内存 ”。
- 从 Thread 对象中选择“可用字节”。
- 选择 添加 。
- 选择“确定”。
- 将示例间隔设置为 1 秒,然后将“下一步”和“完成”。
对话现在应如下所示:
创建调试诊断 1.2 规则
发生高内存条件时收集用户模式进程转储的最简单方法是使用调试诊断(DebugDiag)。可以下载 调试诊断工具 v2 Update 3。
在服务器上安装 DebugDiag 并运行它。 (你将在 安装后开始 菜单。)
找出导致内存泄漏的函数的最重要信息是堆分配的堆栈跟踪。 默认情况下,不会获取这些堆栈跟踪。 可以为每个进程启用此功能。 请使用以下命令来启用堆栈跟踪:
gflags -i w3wp.exe +ust
该命令不会为正在运行的进程启用堆栈跟踪。 因此,对于无法重启的进程(例如服务、lsass、Winlogon),必须重启测试计算机。
使用以下命令验证为特定进程设置了哪些设置:
gflags -i w3wp.exe
运行 DebugDiag 时,会显示 “选择规则类型 ”对话框。 按照以下步骤为应用程序池创建泄漏规则:
选择“本机”(non-.NET)内存>句柄泄漏>下一步。
选择一个进程,然后选择“ 下一步”。
选择配置。
按下图所示设置规则:
如果需要,可以调整这些值,但请注意不要指定少量 MB 来生成吨的转储文件。 在专用字节达到 800 MB 时生成用户转储,并在之后再再生成 100 MB。 当虚拟字节达到 1,024 MB 时生成用户转储,并在之后再再生成 200 MB。
选择保存并关闭。
选择下一步。
如果需要,可以输入规则的名称并记下转储的保存位置。 在需要时可以更改此位置。
选择下一步。
选择“立即>激活规则完成”。
如果收到内存转储时出现内存不足错误,可以手动获取内存转储。
- 选择“ 进程 ”选项卡。
- 右键单击目标进程。
- 选择“ 创建完整用户dump”。
数据分析
在内存不足错误或创建内存转储后,你将有两组要查看的数据;Perfmon 数据收集器集和内存转储。 让我们首先查看 Perfmon 数据。
分析性能数据
若要查看问题的 Perfmon 数据,请右键单击“用户定义的”节点下列出的高内存数据收集器集,然后选择“最新报告”。 可以看到类似于下图的内容:
若要获取导致 CPU 过高问题的根目录,请查看使用 DebugDiag 创建的转储。
使用 DebugDiag 进行转储分析
DebugDiag 可以通过执行自动转储分析来识别许多问题。 对于此特定问题,DebugDiag 的性能分析器非常适合帮助确定高 CPU 问题的根本原因。 若要使用分析器,请执行以下步骤:
- 在 DebugDiag 中选择“高级分析 ”选项卡。
- 选择 “内存压力分析器”。 请确保使用 MemoryAnalysis.asp 而不是 DotNetMemoryAnalysis-BETA.asp。
- 选择“ 添加数据文件”。
- 浏览到创建转储的位置。 默认情况下,这是 C:\Program Files\DebugDiag\Logs 文件夹的子文件夹。
- 选择其中一个转储,然后选择 Ctrl+A 以选择该文件夹中的所有转储。
- 选择打开。
- 选择开始分析。
DebugDiag 需要几分钟时间分析转储并提供分析。 完成分析后,会看到类似于下图的页面:
备注
报告顶部会告诉你检测到内存泄漏。 在“目录”中,你将看到一个指向泄漏分析报告详细信息的链接。 选择该链接,你将看到有关这些前 10 个模块的信息(按分配计数或分配大小)。 下面是示例:
在此分析中,可以看到泄漏通信组件正在运行。 若要进一步查看 泄漏通信的模块信息 ,如下所示,可以看到该方法 CFoo::crtheap
分配未完成的内存。
下一步是查看方法代码 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 数据和堆栈跟踪转储,以帮助缩短周转时间。