使用 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
我在理解故障转储和查找 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