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

Tom*_*uλa 5 debugging wpf memory-leaks windbg dump

我有一些麻烦来理解崩溃转储并找出OutOfMemoryExceptionWPF应用程序抛出的根本原因.应用程序运行几个小时后抛出异常,这清楚地表明存在内存泄漏.

我的第一步是看看!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)
Run Code Online (Sandbox Code Playgroud)

这表明内存非常高.

然后我用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.
Run Code Online (Sandbox Code Playgroud)

请注意,有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[]
Run Code Online (Sandbox Code Playgroud)

据我所知,没有一个占据所有内存的信号对象.最大的一个只有大约122MB.总结所有大小(8500行输出行)给出(1.1GB)占用的内存.似乎所有的对象图都以某种方式重复并添加到内存中并且从未被释放.

!gcroot 6848038c!gcroot 6708fd04检查EffectiveValueEntry和System.String如何到达,永远不会结束,堆栈SOOOO大...

dumpheap -mt <address>并没有告诉我一些让我感到震惊的事情.!finalizequeue表明有很多对象(超过2百万)注册完成:

6708a1b8  2401099     38417584 System.WeakReference
Total 2417538 objects
Run Code Online (Sandbox Code Playgroud)

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

问题如何深入查看问题的根源(我可以使用windbg的其他命令来检查它).因为看起来不只是一个物体在泄漏,而是整个物体图.我是在正确的轨道还是还有其他我忽视的东西?还有其他假设吗?

Tho*_*ler 10

对象图重复

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

GC Heap Size:            Size: 0x448fde94 (1150279316) bytes.
Run Code Online (Sandbox Code Playgroud)

或者总结价值观!dumpheap -stat.

总结所有尺寸(8500行输出线)给出(1.1GB)

这大致关联到显示值<unknown>!address -summary.

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

没有理由假设整个对象图是重复的.这是正常情况.

内存不足

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

004b0890      159     65181140      Free
Run Code Online (Sandbox Code Playgroud)

不幸的是,那些65 MB被分成159个较小的区域.要获得最大块,你需要运行一个!dumpheap -mt 004b0890.

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

--- Usage Summary ---------------- RgnCount ------- Total Size --------
Free                                    518           3f6b000 (  63.418 Mb)
Run Code Online (Sandbox Code Playgroud)

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

--- Largest Region by Usage ----------- Base Address -------- Region Size ----------
Free                                        7752f000            1a1000 (   1.629 Mb)
Run Code Online (Sandbox Code Playgroud)

因此,很明显,该应用程序内存不足.

记忆在哪里?

252 MB是本机堆.看来你正在使用一些原生DLL.虽然目前这似乎不太多,但这一事实可能表明固定物体的存在.固定对象不是垃圾回收.查看输出,!gchandles以确定是否可能是问题的一部分.

188 MB在DLL中.您可以卸载未使用的本机DLL,但对于.NET程序集,您可能无法做很多事情.

堆栈中有125 MB.默认大小为1 MB,您的应用程序中似乎有125个线程.用于!clrstack了解他们正在做什么以及为什么他们还没有完成.潜在地,每个线程都可以处理某些事情,但尚未释放对象.如果你有它在你的控制之下,不要并行启动这么多线程.例如,仅使用8个线程并等待线程完成,然后再完成下一个工作.

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

据我所知,没有一个对象可以占用所有内存.最大的一个只有大约122MB.

请注意,没有一个对象EffectiveValueEntry[]吃122 MB ob内存.其中有847.783.这改变了"为什么这个对象使用这么多内存?"的问题."为什么会有这么多人?" 例如,为什么您的应用程序需要207.806个文本块?它真的显示了这么多文字吗?

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

!gcroot 6848038c
!gcroot 6708fd04
Run Code Online (Sandbox Code Playgroud)

这些都是来自输出的数字!dumpheap -stat.使用它们!gcroot应该给出一个警告

请注意,6848038c不是有效对象.

相反,!gcroot仅适用于!dumpheap没有-stat参数的单个对象.

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

要找出OutOfMemoryException的触发器,可以使用该!u命令.您需要阅读一些MSIL代码才能理解它的作用.另请参见如何识别阵列类型.但是,在您的场景中,我猜这没用,因为即使是小对象也可以触发这个.