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

5

问题背景

在过去的几个月中,我们发现我的在线Asp.net应用程序存在问题。应用程序工作正常,但是每天1或2次它会在不同模块上突然崩溃,但代码中没有任何问题,在本地服务器上也没有这样的问题。

经过一些研究,我发现在IIS上运行我的应用程序的进程内存会持续增加,当达到某个级别时,它就开始崩溃。

临时解决方案:

每当我们发现这种问题时,我们重新启动IIS上的应用程序。 这会终止该进程并启动新进程,然后应用程序开始工作。

有时候一天需要重启应用程序2或3次,有时候更多。

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

经过一些研究,找到了一些解决方案:

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

使用的工具:Windbg

  1. open in Windbg tool for analysis.
  2. write command

     .load by clr
      dumpheap -stat
    

我可以帮助您翻译,这段内容涉及数据类型的参考,作者目前在某些方面遇到了困难,并在图片部分分享了相关信息。

问题:

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?

Output of dumpheap stack

enter image description here

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


1
我建议看一下debugdiag,这是一个更简单的工具,具有内存压力分析功能,基本上您只需附加/打开您的应用程序进行跟踪。然后,您可以设置触发点或生成转储文件,然后使用分析工具执行内存分析,请参见https://blogs.msdn.microsoft.com/asiatech/2014/01/13/debug-diagnostic-2-0-creating-a-memory-leak-rule-unmanaged-code/和https://troubleshootingsql.com/2011/05/27/tools-tips-and-tricks-11-debug-diag-and-memory-leaks/以获取指南。 - EdChum
DebugDiag最初使用起来比较容易,然后我会开始使用processExplorer和windbg来获取更低级别的信息。 - EdChum
这可能有助于使用Windbg https://blogs.msdn.microsoft.com/tess/2008/04/03/net-debugging-demos-lab-7-memory-leak-review/。 - jbl
1个回答

4

当应用程序崩溃时,可以从任务管理器中创建一个转储文件来捕获应用程序进程

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

  • 你没有太多时间去做。只有在崩溃对话框显示的时候才能这样做。如果你迟到了,应用程序就会消失。
  • 在那种状态下,你将很难进行调试。它将显示一个断点而不是原始异常,这是操作系统用于显示对话框和收集诊断数据的。

使用WER本地转储自动创建崩溃转储文件,而不是手动操作。这更加可靠,并给出原始异常。请参见如何为.NET获取良好的崩溃转储文件

我正在寻找内存泄漏问题的正确方法吗?

抱歉,你已经走错了方向。

!dumpheap -stat开始并不是一个好主意。通常情况下,你应该从最低级别开始,也就是!address -summary。它会告诉你是否存在托管内存泄漏或本地内存泄漏。如果是托管泄漏,你可以继续使用!dumpheap -stat

如果我的路径正确,下一步是什么?

即使这不是正确的路径,学会如何确定你走错了也是一个好主意。那么,我怎么知道呢?

通过查看!dumpheap -stat的输出,你可以看到:

[...]
111716     12391360 System.String.

这告诉你有110,000个不同的字符串,使用了12 MB的内存。它还告诉你其他所有东西都少于12 MB。看看其他大小,你会发现.NET不是你的OutOfMemoryException的原因。它们使用不到50 MB。
如果存在托管泄漏,您将寻找对象连接的路径,以便垃圾收集器认为无法释放它。命令是!gcroot

Windbg工具能够很好地查找此类问题吗?

可能可以,但WinDbg不是最好的工具。使用内存分析器代替。那是专用于内存泄漏的工具。通常它具有更好的可用性。不幸的是,您需要决定是否需要托管内存分析器、本机内存分析器或两者兼备。
我曾经写过如何使用WinDbg跟踪.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的一个错误,但4TB不是常见的溢出点。
使用!heap -a 0000018720bd0000,您可以看到这两个虚拟分配:
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内存映射文件。
我不知道您的应用程序在做什么。这真的是我需要停止分析的地方。我希望这对您有所帮助,您可以得出自己的结论并解决问题。

Thomas Weller 感谢您宝贵的时间,但我仍然无法找到解决方案。您能否建议我适当的方法和技巧,帮助我找到内存泄漏的解决方案。一些详细的教程可以详细解释我的问题的解决方案。希望能提供一份一步一步的指南。提前致谢。 - Ali Imran
@AliImran:没有一个“一步一步”的指南。正如您在图表中所看到的,整个过程中需要做出多个决策。因此,根据不同情况会有几个逐步指南。为了提供帮助,我需要额外的信息,这些信息在问题中没有提到。请从!address -summary开始。 - Thomas Weller
Thomas Weller再次感谢您的回复。我与您分享了一个包含我的转储文件的链接。如果您查看并指导我缺少什么,我将不胜感激。我还更新了我的问题,并分享了!address-summary的屏幕截图,也请您查看。再次感谢。https://drive.google.com/file/d/1ok9mWXg3NEIt6boEMjfOx1wXEgmmzSOL/view - Ali Imran
Thomas Weller,是这样吗?我从服务器生成转储文件,并在我的本地机器windbg中进行分析。 Thomas非常感谢您的帮助,我的问题还没有解决,但是您的信息让我接近解决方案。 我仍然对许多地方感到困惑。如果您不介意,可以随时与我交谈,适合您的任何时间,如果您有问题,我可以理解。再次感谢您和您宝贵的时间。 - Ali Imran
@AliImran:是的,在服务器上创建转储文件,然后使用另一台机器进行分析是可行的。我甚至会说这是“正常”的方法,因为生产系统上没有调试工具。我曾经在崩溃转储分析方面提供咨询服务,但我现在不再提供此类服务。我不能在工作时安排电话会议,而且我还有一些其他的爱好需要花时间。然而,如果你有一个明确而简短的问题准备好在[标签:windbg]中发布,我肯定会看到它。 - Thomas Weller
@AliImran:如果你的问题涉及个人数据或需要签署保密协议,你也可以通过电子邮件联系我。但请确保你的问题与在Stack Overflow上提问时一样精准和规范。问题应该是自描述的,提供所有必要的步骤以便跟进,已经做了一些研究并且不应过于宽泛。如果你做好了这些准备,你会更清楚地理解问题,并更好地理解答案。 - Thomas Weller

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接