使用Windbg查找asp.net应用程序中的内存泄漏问题

Ali*_*ran 5 asp.net debugging memory-leaks windbg

问题背景

在过去的几个月里,我们发现我的在线 Asp.net 应用程序存在问题。应用程序工作正常,但一天内有 1 到 2 次突然在实时服务器上的不同模块上崩溃,但它们的代码根本不存在此类问题,并且在本地服务器上没有发现此类问题。

经过一些研究,我发现在实时服务器上的 IIS 上运行我的应用程序的进程其内存不断增加,当达到一定水平时,它开始崩溃。

临时解决方案:

每当我们发现此类问题时,我们都会在 IIS 上重新启动应用程序。它可以结束这个进程并开始新的进程,然后该应用程序开始工作。

一天中的某个时间,我们需要重新启动应用程序 2 到 3 次,有时甚至更多。

我发现的问题:内存泄漏。

经过一番研究后找到一些解决方案:

  1. 当应用程序崩溃时,从任务管理器中的我的应用程序进程创建转储文件。

使用工具:Windbg

  1. 在Windbg工具中打开进行分析。
  2. 写命令

     .load by clr
      dumpheap -stat
    
    Run Code Online (Sandbox Code Playgroud)

显示大量数据类型的引用。现在我陷入了这一点。我在图片部分与您分享。

问题:

1. I am on the right direction in finding memory leaks issue?
2. if my path is right where whats my next step?
3. Windbg tool is good for finding such kind of issue?
Run Code Online (Sandbox Code Playgroud)

dumpheap堆栈的输出

在此输入图像描述

转储文件链接以进行详细审查,当服务器停止响应时我获取此转储文件

Tho*_*ler 4

当应用程序崩溃时,从任务管理器中的我的应用程序进程创建转储文件

这不是一个好的选择,因为

  • 你没有太多时间这样做。仅当显示崩溃对话框时您才能执行此操作。如果你太晚了,申请就会消失。
  • 在这种状态下,您将很难调试它。它将显示一个断点,而不是原始异常,操作系统使用该断点来显示对话框并收集诊断数据

Use WER local dumps to automatically create a crash dump when it crashes instead of doing it manually. It's much more reliable and gives you the original exception. See How do I take a good crash dump for .NET

I am on the right direction in finding memory leaks issue?

Sorry, you're on the wrong track already.

Starting with !dumpheap -stat is not a good idea. Usually one would start at the lowest level, which is !address -summary. It will give you an indicator whether it's a managed memory leak or a native memory leak. If it's a managed leak, you could continue with !dumpheap -stat

if my path is right where whats my next step?

Even if it's not the right path, it's a good idea that you learn how figure out that you're on the wrong path. So, how do I know?

Looking at your output of !dumpheap -stat, you can see

[...]
111716     12391360 System.String.
Run Code Online (Sandbox Code Playgroud)

This tells you there are 110.000 different strings, using 12 MB of memory. It also tells you that everything else takes less than 12 MB. Look at the other sizes and you'll find out that .NET is not the reason for your OutOfMemoryException. They use less than 50 MB.

If there were a managed leak, you would look for paths where objects are connected to, so that the garbage collector thinks it cannot be freed. The command is !gcroot.

Windbg tool is good for finding such kind of issue?

这是可能的,但 WinDbg 不是最好的工具。请改用内存分析器。这是一个专门针对内存泄漏的工具。通常它具有更好的可用性。不幸的是,您需要决定是否需要托管内存分析器、本机内存分析器或两者都需要。

我曾经写过如何使用 WinDbg 来追踪 .NET OutOfMemoryException。您会在那里找到一个图表,它为您提供了如何在不同情况下进行操作的想法。


在你的转储中,我看到 2 TB<unknown>内存,这可能是 .NET,但不一定是.NET。尽管如此,这 2 TB 可能是 OOM 的原因,因为其余的大小小于 350 MB。

由于clr位于已加载模块的列表中,因此我们可以!dumpheap -stat像您一样进行检查。但使用内存的对象并不多。

!eeheap -gc显示有8个堆,对应于你机器的8个处理器,用于并行垃圾收集。最大的单个堆为 45 MB,总共 249 MB。这大致相当于 的总和!dumpheap。结论:.NET 不是罪魁祸首。

让我们检查一下特殊情况:

  1. MSXML 的存在
  2. 位图
  3. 拨打 的呼叫HeapAlloc()太大,因此会直接转发至VirtualAlloc()
  4. 直接致电VirtualAlloc()

MSXML 不存在:lm m msxml*不产生输出。

没有位图:!dumpheap -stat -type Bitmap

大于 512 kB 的堆分配:!heap -stat. 这是输出的截断部分:

0:000> !heap -stat
_HEAP 0000018720bd0000
     Segments            00000006
         Reserved  bytes 0000000001fca000
         Committed bytes 0000000001bb3000
     VirtAllocBlocks     00000002
         VirtAlloc bytes 00000312cdc4b110
_HEAP 0000018bb0fe0000
     Segments            00000005
         Reserved  bytes 0000000000f0b000
         Committed bytes 0000000000999000
     VirtAllocBlocks     00000001
         VirtAlloc bytes 0000018bb0fe0110
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,有 3 个块被分配给 VirtualAlloc。这个尺寸有点不切实际:

0:000> ? 00000312cdc4b110
Evaluate expression: 3379296514320 = 00000312`cdc4b110
0:000> ? 0000018bb0fe0110
Evaluate expression: 1699481518352 = 0000018b`b0fe0110
Run Code Online (Sandbox Code Playgroud)

总计为 3.3TB + 1.7TB = 6TB,而不是 2TB。现在,这可能是 的一个 bug !address,但 4TB 并不是常见的溢出点。

!heap -a 0000018720bd0000可以看到 2 个虚拟分配:

Virtual Alloc List:   18720bd0110
    0000018bac70c000: 00960000 [commited 961000, unused 1000] - busy (b), tail fill
    0000018bad07b000: 00960000 [commited 961000, unused 1000] - busy (b), tail fill
Run Code Online (Sandbox Code Playgroud)

!heap -a 0000018bb0fe0000可以看到第三个:

Virtual Alloc List:   18bb0fe0110
    0000018bb1043000: 00400000 [commited 401000, unused 1000] - busy (b), tail fill
Run Code Online (Sandbox Code Playgroud)

这些都是 4.1MB 和 9.8MB 的相对较小的块。

对于最后一部分,直接调用VirtualAlloc(),您需要返回到 的级别!address。您!address -f:VAR -c:".echo %1 %3"可以看到所有区域的地址和大小<unknown>。您会在那里找到很多条目,其中许多条目尺寸较小,有些可能是 .NET 堆,一些 2GB 条目,还有一个非常大的分配

2GB 的:

0x18722070000 0x2d11000
0x18724d81000 0x7d2ef000
0x187a2070000 0x2ff4000
0x187a5064000 0x7d00c000
0x18822070000 0x2dfe000
0x18824e6e000 0x7d202000
0x188a2070000 0x2c81000
0x188a4cf1000 0x7d37f000
0x18922070000 0x2d13000
0x18924d83000 0x7d2ed000
0x189a2070000 0x2f5a000
0x189a4fca000 0x7d0a6000
0x18a22070000 0x2c97000
0x18a24d07000 0x7d369000
0x18aa2070000 0x2d0c000
0x18aa4d7c000 0x7d2f4000
Run Code Online (Sandbox Code Playgroud)

这些很可能是 .NET 堆(提交部分 + 保留部分)。

大的一个:

0x7df600f57000 0x1ffec56a000
Run Code Online (Sandbox Code Playgroud)

关于它的信息:

0:000> !address 0x7df600f57000 

Usage:                  <unknown>
Base Address:           00007df6`00f57000
End Address:            00007ff5`ed4c1000
Region Size:            000001ff`ec56a000 (   2.000 TB)
State:                  00002000          MEM_RESERVE
Protect:                <info not present at the target>
Type:                   00040000          MEM_MAPPED
Allocation Base:        00007df5`ff340000
Allocation Protect:     00000001          PAGE_NOACCESS
Run Code Online (Sandbox Code Playgroud)

它看起来像一个未使用(因此被保留)的 2TB 内存映射文件。

我不知道你的应用程序在做什么。这确实是我需要停止分析的地方。我希望这对您有所帮助,您可以得出结论并解决问题。