【问题标题】:Analyzing the root cause of OutOfMemoryException in WPF app with WinDbg使用 WinDbg 分析 WPF 应用中 OutOfMemoryException 的根本原因
【发布时间】:2015-05-26 13:24:03
【问题描述】:

我在理解故障转储和查找 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 &lt;address&gt; 没有向我展示令我震惊的东西。 !finalizequeue 表明有许多对象(超过 200 万个)注册完成:

6708a1b8  2401099     38417584 System.WeakReference
Total 2417538 objects

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

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

【问题讨论】:

  • 也可以尝试使用 CLR Profiler,它可以很容易地为您提供很多复杂的信息。您可以使用!TraverseHeap 为CLR Profiler 导出必要的日志。最重要的是,它使显示对象分配图变得非常容易。但是,我担心它可能对您没有帮助 - 我认为 WPF 本身存在一些烦人的内存泄漏,环顾四周可能会帮助您找到这个问题。
  • @Luaan 不幸的是,我只有故障转储,可以进行事后调试。无论如何,感谢 CLR 提供商的成功。 !TraverseHeap 命令是特定于 CLR Profiler 的吗?
  • 您可以将!TraverseHeap 生成的文件加载到CLR Profiler 中,因此故障转储就足够了——在WinDbg 中打开它,运行!TraverseHeap,然后在CLR Profiler 中打开生成的文件。您也可以自己解析文件,它是一个简单的文本文件。
  • 但它位于不同的模块中,因为仅加载了 SOS,我就进入了 windbg“未找到导出 TraversHeap”错误
  • 可能在 SOSex 中。尝试谷歌搜索,我相信你会发现什么是错的。哦,确保你指定一个文件名作为参数。

标签: wpf debugging memory-leaks windbg dump


【解决方案1】:

对象图复制

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

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

或将!dumpheap -stat 的值相加。

总结所有大小(8500 行输出行)得出 (1.1GB)

这大致与在!address -summary 中显示为&lt;unknown&gt; 的值相关。

--- 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 个较小的区域。要获得其中最大的块,您需要运行 !dumpheap -mt 004b0890

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

--- 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 对象使用的。但是,您得出了一些错误的结论。

据我了解,没有一个对象会占用所有内存。最大的只有大约 122MB。

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

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

!gcroot 6848038c
!gcroot 6708fd04

这两个数字都来自!dumpheap -stat 的输出。在!gcroot 中使用它们应该会发出类似警告

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

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

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

要找出 OutOfMemoryException 的触发器,您可以使用!u 命令。您需要阅读一些 MSIL 代码以了解它的作用。另见How to identify array type。但是,在您的情况下,我想这没用,因为即使是小物体也可能触发此问题。

【讨论】:

  • 嗨@Thomas。感谢您提供所有这些知识。我在我的问题中使用gcroot 命令犯了一个错误。我在dumpheap -mt 6848038c 之前做,然后我选择对象地址并 gcroot 它。但它永远不会结束。我走每一条线
【解决方案2】:

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

【讨论】:

    猜你喜欢
    • 2019-03-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-17
    • 1970-01-01
    • 2023-01-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多