使用 WinDbg 分析 WPF 应用程序中 OutOfMemoryException 的根本原因

Analyzing the root cause of OutOfMemoryException in WPF app with WinDbg

我在理解故障转储和查找 WPF 应用程序抛出 OutOfMemoryException 的根本原因时遇到了一些麻烦。在应用程序 运行 数小时后抛出异常,因此这清楚地表明存在内存泄漏。

我的第一步是查看 !address -summary 命令:

--- Usage Summary ---------------- RgnCount ------- Total Size -------- %ofBusy %ofTotal
<unknown>                              2043          58997000 (   1.384 Gb)  71.43%   69.22%
Heap                                    152           fcc3000 ( 252.762 Mb)  12.74%   12.34%
Image                                  1050           bc77000 ( 188.465 Mb)   9.50%    9.20%
Stack                                   699           7d00000 ( 125.000 Mb)   6.30%    6.10%
Free                                    518           3f6b000 (  63.418 Mb)            3.10%
TEB                                     125             7d000 ( 500.000 kb)   0.02%    0.02%
Other                                    12             36000 ( 216.000 kb)   0.01%    0.01%
PEB                                       1              1000 (   4.000 kb)   0.00%    0.00%

--- Type Summary (for busy) ------ RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_PRIVATE                            2186          685b7000 (   1.631 Gb)  84.14%   81.53%
MEM_IMAGE                              1710           f3f3000 ( 243.949 Mb)  12.29%   11.91%
MEM_MAPPED                              186           46db000 (  70.855 Mb)   3.57%    3.46%

--- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_COMMIT                             3366          73fe7000 (   1.812 Gb)  93.52%   90.62%
MEM_RESERVE                             716           809e000 ( 128.617 Mb)   6.48%    6.28%
MEM_FREE                                518           3f6b000 (  63.418 Mb)            3.10%

--- Protect Summary (for commit) - RgnCount ----------- Total Size -------- %ofBusy %ofTotal
PAGE_READWRITE                         1650          5e19e000 (   1.470 Gb)  75.87%   73.52%
PAGE_EXECUTE_READ                       224           bc42000 ( 188.258 Mb)   9.49%    9.19%
PAGE_READWRITE|PAGE_WRITECOMBINE         28           439f000 (  67.621 Mb)   3.41%    3.30%
PAGE_READONLY                           573           3d7b000 (  61.480 Mb)   3.10%    3.00%
PAGE_WRITECOPY                          214            f8f000 (  15.559 Mb)   0.78%    0.76%
PAGE_EXECUTE_READWRITE                  265            d0a000 (  13.039 Mb)   0.66%    0.64%
PAGE_READWRITE|PAGE_GUARD               357            33b000 (   3.230 Mb)   0.16%    0.16%
PAGE_EXECUTE_WRITECOPY                   55            119000 (   1.098 Mb)   0.06%    0.05%

--- Largest Region by Usage ----------- Base Address -------- Region Size ----------
<unknown>                                   78d40000           2350000 (  35.313 Mb)
Heap                                        36db0000            fd0000 (  15.813 Mb)
Image                                       64a8c000            e92000 (  14.570 Mb)
Stack                                        4b90000             fd000 (1012.000 kb)
Free                                        7752f000            1a1000 (   1.629 Mb)
TEB                                         7ede3000              1000 (   4.000 kb)
Other                                       7efb0000             23000 ( 140.000 kb)
PEB                                         7efde000              1000 (   4.000 kb)

这说明内存比较大

然后我正在使用 eeheap -gc 命令查看 GC 堆大小。它显示堆很大 (1.1GB),这表明应用程序的托管部分存在问题。

5fc90000  5fc91000  60c7acd4  0xfe9cd4(16686292)
5a060000  5a061000  5b05e9c0  0xffd9c0(16767424)
56de0000  56de1000  57ddf1c4  0xffe1c4(16769476)
57de0000  57de1000  58ddbbbc  0xffabbc(16755644)
73ff0000  73ff1000  74fe0f5c  0xfeff5c(16711516)
50de0000  50de1000  51dcfa58  0xfeea58(16706136)
5b060000  5b061000  5c05ca54  0xffba54(16759380)
4fde0000  4fde1000  50ddfd8c  0xffed8c(16772492)
Large object heap starts at 0x03921000
 segment     begin allocated  size
03920000  03921000  049013d0  0xfe03d0(16647120)
14850000  14851000  15837380  0xfe6380(16671616)
178d0000  178d1000  1889a3e0  0xfc93e0(16552928)
1a1c0000  1a1c1000  1b1abca8  0xfeaca8(16690344)
40de0000  40de1000  41dc8b48  0xfe7b48(16677704)
42de0000  42de1000  43827170  0xa46170(10772848)
54de0000  54de1000  55dd6d18  0xff5d18(16735512)
Total Size:              Size: 0x448fde94 (1150279316) bytes.
------------------------------
GC Heap Size:            Size: 0x448fde94 (1150279316) bytes.

请注意,有 64 个段,每个段大约 (16MB)。好像有一些数据一直保存在内存中,一直没有释放。

接下来我看!dumpheap -stat:

65c1f26c   207530     19092760 System.Windows.Media.GlyphRun
65c2c434   373991     20943496 System.Windows.Media.RenderData
68482bb0   746446     26872056 MS.Utility.ThreeItemList`1[[System.Double, mscorlib]]
65c285b4   746448     29857920 System.Windows.Media.DoubleCollection
64c25d58   299568     32353344 System.Windows.Data.BindingExpression
6708a1b8  2401099     38417584 System.WeakReference
67082c2c  1288315     41226080 System.EventHandler
67046f80  1729646     42238136 System.Object[]
64c1409c   206969     52156188 System.Windows.Controls.ContentPresenter
67094c9c   382163     64812664 System.Byte[]
004b0890      159     65181140      Free
64c150d0   207806     72316488 System.Windows.Controls.TextBlock
6708fd04  1498498     97863380 System.String
6848038c   847783    128775772 System.Windows.EffectiveValueEntry[]

据我了解,没有一个对象会占用所有内存。最大的一个只有 122MB 左右。将所有大小(8500 行输出行)相加得出占用的内存 (1.1GB)。似乎所有的对象图都以某种方式被复制并添加到内存中并且从未被释放。

!gcroot 6848038c!gcroot 6708fd04 检查 EffectiveValueEntry 和 System.String 是如何到达的,永无止境,堆栈太大...

dumpheap -mt <address> 没有显示让我感到震惊的内容。 !finalizequeue 显示有许多对象(超过 200 万)注册为最终确定:

6708a1b8  2401099     38417584 System.WeakReference
Total 2417538 objects

我怀疑 OutOfMemoryException 当应用程序试图复制对象图并分配新内存时发生,但我找不到它的根本原因。

问题我怎样才能深入到问题的根源(我可以使用其他什么windbg命令来检查它)。似乎不仅仅是一个对象在泄漏,而是整个对象图都在泄漏。我是在正确的轨道上还是还有其他我忽略的东西?其他假设是什么?

对象图重复

您的应用程序使用 .NET 约 1.1 GB 的虚拟内存。从!eeheap -gc的输出直接可以看出

GC Heap Size:            Size: 0x448fde94 (1150279316) bytes.

或将 !dumpheap -stat.

的值相加

Summing up all the sizes (8500 lines of output lines) gives the (1.1GB)

这与 !address -summary 中显示为 <unknown> 的值大致相关。

--- Usage Summary ---------------- RgnCount ------- Total Size -------- %ofBusy %ofTotal
<unknown>                              2043          58997000 (   1.384 Gb)  71.43%   69.22%

没有理由假设正在复制整个对象图。这是正常情况。

内存不足

目前,.NET 已经提交并标记为空闲的 65 MB 虚拟内存(来自 !dumpheap -stat):

004b0890      159     65181140      Free

不幸的是,这 65 MB 被分成了 159 个较小的区域。要获得其中最大的块,您需要 运行 a !dumpheap -mt 004b0890.

此外,.NET 可以从 Windows(来自 !address -summary)获得另外 63 MB:

--- Usage Summary ---------------- RgnCount ------- Total Size --------
Free                                    518           3f6b000 (  63.418 Mb)

但是最大的块只有 1.6 MB,所以几乎没用:

--- Largest Region by Usage ----------- Base Address -------- Region Size ----------
Free                                        7752f000            1a1000 (   1.629 Mb)

很明显,应用程序 内存不足。

内存在哪里?

252 MB 位于本机堆中。看来您正在使用一些本机 DLL。虽然目前这似乎并不过分,但这一事实可能表明存在固定对象。固定对象不会被垃圾回收。查看 !gchandles 的输出,找出这是否是问题的一部分。

188 MB 在 DLL 中。您可以卸载未使用的本机 DLL,但对于 .NET 程序集,您可能无能为力。

堆栈中有 125 MB。默认大小为 1 MB,您的应用程序中似乎有 125 个线程。使用 !clrstack 找出他们在做什么以及为什么他们还没有完成。潜在地,每个线程都在处理某些东西并且还没有释放对象。如果您可以控制它,请不要并行启动那么多线程。例如。仅使用 8 个线程并等待线程完成后再进行下一项工作。

当然,.NET 对象使用了大部分内存。但是,你得出了一些错误的结论。

As I understand it, there is no a single object that takes all the memory. The biggest one is just about 122MB.

请注意,没有一个对象 EffectiveValueEntry[] 占用 122 MB ob 内存。其中有 847.783 个。这会将问题从 "Why does this object use so much memory?" 更改为 "Why are there so many of them?"。例如,为什么您的应用程序需要 207.806 个文本块?真的显示这么多文字吗?

使用 !gcroot 是个好主意。但是,您将它与方法 table 而不是对象的地址一起使用:

!gcroot 6848038c
!gcroot 6708fd04

这些都是 !dumpheap -stat 输出的数字。在 !gcroot 中使用它们应该给出类似

的警告

Please note that 6848038c is not a valid object.

相反,!gcroot 仅适用于您从没有 -stat 参数的 !dumpheap 获得的单个对象。

您可以使用 !traveseheap filename.log 将所有对象转储到与 CLR profiler [Codeplex] 兼容的文件中。请注意,CLR Profiler 无法读取 -xml 格式。加载对象信息后,Heap Graph 可能是对您最有用的按钮。

要找出OutOfMemoryException 的触发因素,可以使用!u 命令。您需要阅读一些 MSIL 代码才能了解它的作用。另见 How to identify array type。但是,在您的场景中,我想那是没有用的,因为即使是小物体也可能触发它。

除了其他答案,您可以尝试使用 WinDbg 扩展可视化内存地址和 GC 堆 cosos gcview