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

Using Windbg to find Memory leak issue in asp.net Application

Problem Background

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

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

Temporary solution:

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

一天中的某个时候我们需要重新启动我们的应用程序 2 或 3 次。

Problem that i find: memory leak.

Find some solution after some research:

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

Tool use: Windbg

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

     .load by clr
      dumpheap -stat
    

显示大量数据类型引用。 现在我停留在这一点上。 我在图片部分与您分享。

Question:

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?

dump file link 为了进行详细审查,我在服务器停止响应时获取此转储文件

create a dump file from my application process from task manager when application is crashes

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

  • 您没有太多时间这样做。只要显示崩溃对话框,您就只能这样做。如果你来不及,申请就没了。
  • 在那种状态下,您将很难调试它。它会显示一个断点,而不是原来的异常,OS 使用它来显示对话框和收集诊断数据

使用 WER 本地转储在崩溃时自动创建故障转储,而不是手动创建。它更可靠,并为您提供原始异常。参见 How do I take a good crash dump for .NET

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

抱歉,您已经走错路了。

!dumpheap -stat 开始不是一个好主意。通常会从最低级别开始,即 !address -summary。它会给你一个指标,它是托管内存泄漏还是本机内存泄漏。如果这是一个管理泄漏,你可以继续 !dumpheap -stat

if my path is right where whats my next step?

即使这条路不对,你最好学会如何弄清楚自己走错了路。那么,我怎么知道?

查看 !dumpheap -stat 的输出,您可以看到

[...]
111716     12391360 System.String.

这告诉您有 110.000 个不同的字符串,使用 12 MB 的内存。它还会告诉您其他一切都需要不到 12 MB。查看其他大小,您会发现 .NET 不是 OutOfMemoryException 的原因。他们使用不到 50 MB。

如果存在管理泄漏,您将查找对象连接到的路径,以便垃圾收集器认为它无法被释放。命令为!gcroot.

Windbg tool is good for finding such kind of issue?

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

我曾经写过how to use WinDbg to track down .NET OutOfMemoryException。你会在那里找到一张图表,它可以让你了解如何在不同情况下进行操作。


在您的转储中,我看到 2 TB 的 <unknown> 内存,可能是 .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

如您所见,有 3 个块进入了 VirtualAlloc。尺寸有点不切实际:

0:000> ? 00000312cdc4b110
Evaluate expression: 3379296514320 = 00000312`cdc4b110
0:000> ? 0000018bb0fe0110
Evaluate expression: 1699481518352 = 0000018b`b0fe0110

总计 3.3TB + 1.7TB = 6TB 而不是 2TB。现在,这可能是!address的一个bug,但是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

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

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

这些都是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

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

大的:

0x7df600f57000 0x1ffec56a000

相关信息:

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

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

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