当应用程序崩溃时,可以从任务管理器中创建一个转储文件来捕获应用程序进程
这不是一个好的选择,因为:
- 你没有太多时间去做。只有在崩溃对话框显示的时候才能这样做。如果你迟到了,应用程序就会消失。
- 在那种状态下,你将很难进行调试。它将显示一个断点而不是原始异常,这是操作系统用于显示对话框和收集诊断数据的。
使用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不是罪魁祸首。
让我们检查特殊情况:
- 存在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
的一个错误,但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内存映射文件。
我不知道您的应用程序在做什么。这真的是我需要停止分析的地方。我希望这对您有所帮助,您可以得出自己的结论并解决问题。