当应用程序崩溃时,从任务管理器中的应用程序进程创建转储文件
这不是一个好的选择,因为
- 您没有太多时间这样做。只要显示崩溃对话框,您就可以这样做。如果你太晚了,申请就会消失。
- 在这种状态下,您将难以调试它。它将显示一个断点,而不是原始异常,操作系统使用该断点来显示对话框并收集诊断数据
使用 WER 本地转储在崩溃时自动创建崩溃转储,而不是手动创建。它更可靠,并为您提供原始异常。见How do I take a good crash dump for .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 不是最好的工具。请改用内存分析器。这是内存泄漏的专用工具。通常它具有更好的可用性。不幸的是,您需要决定是否需要托管内存分析器、本机内存分析器或两者兼而有之。
我曾经写过how to use WinDbg to track down .NET OutOfMemoryException。您会在那里找到一张图表,该图表可让您了解如何在不同情况下进行操作。
在您的转储中,我看到 2 TB 的 <unknown> 内存,可能是 .NET,但 needn't be。不过,这 2 TB 可能是 OOM 的原因,因为其余的大小小于 350 MB。
由于clr 在已加载模块列表中,我们可以像您一样检查!dumpheap -stat。但是使用内存的对象并不多。
!eeheap -gc显示有8个堆,对应你机器的8个处理器,用于并行垃圾回收。最大的单个堆为 45 MB,总共 249 MB。这大致匹配!dumpheap 的总和。结论:.NET 不是罪魁祸首。
让我们检查一下特殊情况:
- 存在 MSXML
- 位图
- 对
HeapAlloc() 的呼叫非常大,以至于直接转发到VirtualAlloc()。
- 直接调用
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 的内存映射文件,它未被使用(因此被保留)。
我不知道您的应用程序在做什么。这确实是我需要停止分析的地方。我希望这对您有所帮助,您可以得出结论并解决问题。