如何分析windbg中的<unclassified>内存使用情况
Posted
技术标签:
【中文标题】如何分析windbg中的<unclassified>内存使用情况【英文标题】:How to analyze <unclassified> memory usage in windbg 【发布时间】:2012-02-19 20:58:40 【问题描述】:这是一个在 x64 机器上运行的 .NET v4 Windows 服务应用程序。在稳定运行几天后的某个时候,Windows 服务的内存消耗会像疯了一样猛增,直到它崩溃。我能够以 1.2 GB 捕获它并捕获内存转储。这是我得到的
如果我在我的转储文件上运行 !address -summary in windbg,我会得到以下结果
!地址-摘要
--- Usage Summary ------ RgnCount ------- Total Size -------- %ofBusy %ofTotal
Free 821 7ff`7e834000 ( 7.998 Tb) 99.98%
<unclassified> 3696 0`6eece000 ( 1.733 Gb) 85.67% 0.02%
Image 1851 0`0ea6f000 ( 234.434 Mb) 11.32% 0.00%
Stack 1881 0`03968000 ( 57.406 Mb) 2.77% 0.00%
TEB 628 0`004e8000 ( 4.906 Mb) 0.24% 0.00%
NlsTables 1 0`00023000 ( 140.000 kb) 0.01% 0.00%
ActivationContextData 3 0`00006000 ( 24.000 kb) 0.00% 0.00%
CsrSharedMemory 1 0`00005000 ( 20.000 kb) 0.00% 0.00%
PEB 1 0`00001000 ( 4.000 kb) 0.00% 0.00%
-
-
-
--- Type Summary (for busy) -- RgnCount ----- Total Size ----- %ofBusy %ofTotal
MEM_PRIVATE 5837 0`7115a000 ( 1.767 Gb) 87.34% 0.02%
MEM_IMAGE 2185 0`0f131000 (241.191 Mb) 11.64% 0.00%
MEM_MAPPED 40 0`01531000 ( 21.191 Mb) 1.02% 0.00%
-
-
--- State Summary ------------ RgnCount ------ Total Size ---- %ofBusy %ofTotal
MEM_FREE 821 7ff`7e834000 ( 7.998 Tb) 99.98%
MEM_COMMIT 6127 0`4fd5e000 ( 1.247 Gb) 61.66% 0.02%
MEM_RESERVE 1935 0`31a5e000 (794.367 Mb) 38.34% 0.01%
-
-
--Protect Summary(for commit)- RgnCount ------ Total Size --- %ofBusy %ofTotal
PAGE_READWRITE 3412 0`3e862000 (1000.383 Mb) 48.29% 0.01%
PAGE_EXECUTE_READ 220 0`0b12f000 ( 177.184 Mb) 8.55% 0.00%
PAGE_READONLY 646 0`02fd0000 ( 47.813 Mb) 2.31% 0.00%
PAGE_WRITECOPY 410 0`01781000 ( 23.504 Mb) 1.13% 0.00%
PAGE_READWRITE|PAGE_GUARD 1224 0`012f2000 ( 18.945 Mb) 0.91% 0.00%
PAGE_EXECUTE_READWRITE 144 0`007b9000 ( 7.723 Mb) 0.37% 0.00%
PAGE_EXECUTE_WRITECOPY 70 0`001cd000 ( 1.801 Mb) 0.09% 0.00%
PAGE_EXECUTE 1 0`00004000 ( 16.000 kb) 0.00% 0.00%
-
-
--- Largest Region by Usage ----Base Address -------- Region Size ----------
Free 0`8fff0000 7fe`59050000 ( 7.994 Tb)
<unclassified> 0`80d92000 0`0f25e000 ( 242.367 Mb)
Image fe`f6255000 0`0125a000 ( 18.352 Mb)
Stack 0`014d0000 0`000fc000 (1008.000 kb)
TEB 0`7ffde000 0`00002000 ( 8.000 kb)
NlsTables 7ff`fffb0000 0`00023000 ( 140.000 kb)
ActivationContextData 0`00030000 0`00004000 ( 16.000 kb)
CsrSharedMemory 0`7efe0000 0`00005000 ( 20.000 kb)
PEB 7ff`fffdd000 0`00001000 ( 4.000 kb)
首先,为什么未分类的文件一次显示为 1.73 GB,另一次显示为 242 MB。 (已回答。谢谢)
其次,我知道未分类可能意味着托管代码,但是根据 !eeheap 我的堆大小仅为 248 MB,实际上与 242 匹配,但甚至不接近 1.73GB。转储文件大小为 1.2 GB,远高于正常值。我从哪里去找出所有内存的使用情况。托管堆世界中的任何内容都低于 248 MB,但我使用的是 1.2 GB。
谢谢
编辑
如果我执行 !heap -s 我会得到以下结果
LFH Key : 0x000000171fab7f20
Termination on corruption : ENABLED
Heap Flags Reserv Commit Virt Free List UCR Virt Lock Fast
(k) (k) (k) (k) length blocks cont. heap
-------------------------------------------------------------------------------------
Virtual block: 00000000017e0000 - 00000000017e0000 (size 0000000000000000)
Virtual block: 0000000045bd0000 - 0000000045bd0000 (size 0000000000000000)
Virtual block: 000000006fff0000 - 000000006fff0000 (size 0000000000000000)
0000000000060000 00000002 113024 102028 113024 27343 1542 11 3 1c LFH
External fragmentation 26 % (1542 free blocks)
0000000000010000 00008000 64 4 64 1 1 1 0 0
0000000000480000 00001002 3136 1380 3136 20 8 3 0 0 LFH
0000000000640000 00041002 512 8 512 3 1 1 0 0
0000000000800000 00001002 3136 1412 3136 15 7 3 0 0 LFH
00000000009d0000 00001002 3136 1380 3136 19 7 3 0 0 LFH
00000000008a0000 00041002 512 16 512 3 1 1 0 0
0000000000630000 00001002 7232 3628 7232 18 53 4 0 0 LFH
0000000000da0000 00041002 1536 856 1536 1 1 2 0 0 LFH
0000000000ef0000 00041002 1536 944 1536 4 12 2 0 0 LFH
00000000034b0000 00001002 1536 1452 1536 6 17 2 0 0 LFH
00000000019c0000 00001002 3136 1396 3136 16 6 3 0 0 LFH
0000000003be0000 00001002 1536 1072 1536 5 7 2 0 3 LFH
0000000003dc0000 00011002 512 220 512 100 60 1 0 2
0000000002520000 00001002 512 8 512 3 2 1 0 0
0000000003b60000 00001002 339712 168996 339712 151494 976 116 0 18 LFH
External fragmentation 89 % (976 free blocks)
Virtual address fragmentation 50 % (116 uncommited ranges)
0000000003f20000 00001002 64 8 64 3 1 1 0 0
0000000003d90000 00001002 64 8 64 3 1 1 0 0
0000000003ee0000 00001002 64 16 64 11 1 1 0 0
-------------------------------------------------------------------------------------
【问题讨论】:
使用 SOS dll 并以这种方式查看堆。由于这是一个 .net 程序,因此 .net 框架进行的堆分配不会显示在非托管堆中。 根据 !eeheap (sos.dll) 我的堆大小只有 248 MB。所以我不确定这是 1.2 GB 进程大小的原因,也不是未分类 1.7GB 的原因,除非我遗漏了什么 你们的服务是做什么的?您的服务是否包含非托管或 C++/CLI 代码?它看起来像一个非托管内存泄漏。 GDI、用户对象、句柄计数说明了什么?您的线程卡在哪些调用堆栈中? ~*e!ClrStack 和 ~*e!DumpStack 和 ~*ekv 是您的朋友,可以查看您的线程在做什么。有一个线程在分配东西吗? @AloisKraus 感谢您的回复。没有直接的非托管调用。很难从事物的线程方面弄清楚事情,因为我有很多(大约 600 个)。没有人专门分配任何大量内存。许多人正在做与网络相关的事情(WMI 等),这是预期的。我不确定我是否知道如何查询 GDI 或用户对象,或者这些数字的好/坏 600 个线程?默认情况下,CLR 确实使用 1 MB 的堆栈空间,这是默认提交的。在这种情况下,您已经将 600 MB 内存仅用于线程堆栈。但是您的内存转储仅显示 57.406,即大约 58 个线程。您最大的线程堆栈正好是 1 MB,这可能表明存在 ***。 WMI 疯狂地使用 COM。我很可能是您经常使用某种形式的“WITHIN 0.1”查询 WMI,这会产生大量垃圾 COM 对象。 【参考方案1】:我最近遇到了一个非常相似的情况,并发现了一些对调查有用的技术。没有一个是灵丹妙药,但每一个都更能说明问题。
1) 来自 SysInternals (http://technet.microsoft.com/en-us/sysinternals/dd535533) 的 vmmap.exe 很好地关联了本机和托管内存的信息,并在一个漂亮的 UI 中呈现。可以使用以下技术收集相同的信息,但这更容易,也是一个不错的起点。遗憾的是,它不适用于转储文件,您需要一个实时进程。
2) “!address -summary”输出是更详细的“!address”输出的汇总。我发现将详细输出放入 Excel 并运行一些数据透视表很有用。使用这种技术,我发现被列为“”的大量字节实际上是 MEM_IMAGE 页面,可能是在加载 DLL 时加载的数据页面的副本,然后在数据更改时复制。我还可以过滤到大区域并深入研究特定地址。用牙签在内存转储中四处寻找并进行大量祈祷是痛苦的,但也可以揭示。
3) 最后,我做了一个穷人版的 vmmap.exe 技术。我加载了转储文件,打开了一个日志,然后运行了!address、!eeheap、!heap 和!threads。我还使用 !teb 定位了 ~*k 中列出的线程环境块。我关闭了日志文件并将其加载到我最喜欢的编辑器中。然后我可以找到一个未分类的块并搜索以查看它是否从更详细的命令之一的输出中弹出。您可以很快将本地堆和托管堆关联起来,以将那些从可疑的未分类区域中剔除。
这些都太手动了。我很想编写一个脚本,其输出类似于我在上面的技术 3 中生成的输出,并输出一个适合查看 vmmap.exe 的 mmp 文件。总有一天。
最后一点:我在 vmmap.exe 的输出与 !address 输出之间进行了关联,并注意到 vmmap 对从各种来源识别的这些类型的区域(类似于 !heap 和 !eeheap 使用的区域)但是 !address 没有不知道。也就是说,这些是 vmmap.exe 标记但 !address 没有标记的内容:
.data
.pdata
.rdata
.text
64-bit thread stack
Domain 1
Domain 1 High Frequency Heap
Domain 1 JIT Code Heap
Domain 1 Low Frequency Heap
Domain 1 Virtual Call Stub
Domain 1 Virtual Call Stub Lookup Heap
Domain 1 Virtual Call Stub Resolve Heap
GC
Large Object Heap
Native heaps
Thread Environment Blocks
仍有很多“私有”字节下落不明,但同样,如果我能清除这些问题,我就能缩小问题范围。
希望这能给您一些关于如何调查的想法。我在同一条船上,所以我也会很感激你的发现。谢谢!
【讨论】:
“我很想写一个脚本……用于查看 vmmap”:你有机会这样做吗?【参考方案2】:“使用摘要”告诉您有 3696 个未分类区域,总共 17.33 Gb
“最大区域”表示最大的未分类区域为 242 Mb。 其余未分类的区域(3695 个区域)加起来相差 17.33 Gb。
尝试做一个 !heap –s 并总结 Virt col 以查看本机堆的大小,我认为这些也属于非托管存储桶。 (NB 早期版本显示来自 !address -summary 的本地堆显式)
【讨论】:
感谢使用摘要与最大区域的解释。我已经完成了一个 !heap -s 并总计了 virt col,我得到了 359.12 MB。这说明什么?我添加了 !heap -s 的结果 不抱歉,因为这是一个 64 位转储,我没有实际经验。顺便说一句,转储来自哪个版本的 .NET?其他可能了解更多的人可能会对此感兴趣 我已将该信息添加到问题描述中。它是 .NET 4.0【参考方案3】:我保留了一份 Debugging Tools for Windows 6.11.1.404 的副本,它似乎能够显示对“未分类”更有意义的内容
在那个版本中,我看到了一个 TEB 地址列表,然后是这个:
0:000> !address -summary
--------- PEB fffde000 not found ----
TEB fffdd000 in range fffdb000 fffde000
TEB fffda000 in range fffd8000 fffdb000
...snip...
TEB fe01c000 in range fe01a000 fe01d000
ProcessParametrs 002c15e0 in range 002c0000 003c0000
Environment 002c0810 in range 002c0000 003c0000
-------------------- Usage SUMMARY --------------------------
TotSize ( KB) Pct(Tots) Pct(Busy) Usage
41f08000 ( 1080352) : 25.76% 34.88% : RegionUsageIsVAD
42ecf000 ( 1096508) : 26.14% 00.00% : RegionUsageFree
5c21000 ( 94340) : 02.25% 03.05% : RegionUsageImage
c900000 ( 205824) : 04.91% 06.64% : RegionUsageStack
0 ( 0) : 00.00% 00.00% : RegionUsageTeb
68cf8000 ( 1717216) : 40.94% 55.43% : RegionUsageHeap
0 ( 0) : 00.00% 00.00% : RegionUsagePageHeap
0 ( 0) : 00.00% 00.00% : RegionUsagePeb
0 ( 0) : 00.00% 00.00% : RegionUsageProcessParametrs
0 ( 0) : 00.00% 00.00% : RegionUsageEnvironmentBlock
Tot: ffff0000 (4194240 KB) Busy: bd121000 (3097732 KB)
-------------------- Type SUMMARY --------------------------
TotSize ( KB) Pct(Tots) Usage
42ecf000 ( 1096508) : 26.14% : <free>
5e6e000 ( 96696) : 02.31% : MEM_IMAGE
28ed000 ( 41908) : 01.00% : MEM_MAPPED
b49c6000 ( 2959128) : 70.55% : MEM_PRIVATE
-------------------- State SUMMARY --------------------------
TotSize ( KB) Pct(Tots) Usage
9b4d1000 ( 2544452) : 60.67% : MEM_COMMIT
42ecf000 ( 1096508) : 26.14% : MEM_FREE
21c50000 ( 553280) : 13.19% : MEM_RESERVE
Largest free region: Base bc480000 - Size 38e10000 (931904 KB)
使用我的“当前”版本 (6.12.2.633),我从同一个转储中得到了这个。我注意到两件事:
数据似乎是 HeapAlloc/RegionUsageHeap 和 VirtualAlloc/RegionUsageIsVAD 的总和。
可爱的 EFAIL 错误无疑是丢失数据的部分原因!
我不确定这将如何帮助您处理托管代码,但我认为它实际上回答了最初的问题 ;-)
0:000> !address -summary
Failed to map Heaps (error 80004005)
--- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
<unclassified> 7171 aab21000 ( 2.667 Gb) 90.28% 66.68%
Free 637 42ecf000 ( 1.046 Gb) 26.14%
Stack 603 c900000 ( 201.000 Mb) 6.64% 4.91%
Image 636 5c21000 ( 92.129 Mb) 3.05% 2.25%
TEB 201 c9000 ( 804.000 kb) 0.03% 0.02%
ActivationContextData 14 11000 ( 68.000 kb) 0.00% 0.00%
CsrSharedMemory 1 5000 ( 20.000 kb) 0.00% 0.00%
--- Type Summary (for busy) ------ RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_PRIVATE 7921 b49c6000 ( 2.822 Gb) 95.53% 70.55%
MEM_IMAGE 665 5e6e000 ( 94.430 Mb) 3.12% 2.31%
MEM_MAPPED 40 28ed000 ( 40.926 Mb) 1.35% 1.00%
--- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_COMMIT 5734 9b4d1000 ( 2.427 Gb) 82.14% 60.67%
MEM_FREE 637 42ecf000 ( 1.046 Gb) 26.14%
MEM_RESERVE 2892 21c50000 ( 540.313 Mb) 17.86% 13.19%
--- Protect Summary (for commit) - RgnCount ----------- Total Size -------- %ofBusy %ofTotal
PAGE_READWRITE 4805 942bd000 ( 2.315 Gb) 78.37% 57.88%
PAGE_READONLY 215 3cbb000 ( 60.730 Mb) 2.01% 1.48%
PAGE_EXECUTE_READ 78 2477000 ( 36.465 Mb) 1.21% 0.89%
PAGE_WRITECOPY 74 75b000 ( 7.355 Mb) 0.24% 0.18%
PAGE_READWRITE|PAGE_GUARD 402 3d6000 ( 3.836 Mb) 0.13% 0.09%
PAGE_EXECUTE_READWRITE 80 3b0000 ( 3.688 Mb) 0.12% 0.09%
PAGE_EXECUTE_WRITECOPY 80 201000 ( 2.004 Mb) 0.07% 0.05%
--- Largest Region by Usage ----------- Base Address -------- Region Size ----------
<unclassified> 786000 17d9000 ( 23.848 Mb)
Free bc480000 38e10000 ( 910.063 Mb)
Stack 6f90000 fd000 (1012.000 kb)
Image 3c3c000 ebe000 ( 14.742 Mb)
TEB fdf8f000 1000 ( 4.000 kb)
ActivationContextData 190000 4000 ( 16.000 kb)
CsrSharedMemory 7efe0000 5000 ( 20.000 kb)
【讨论】:
【参考方案4】:最好的办法是在 windbg (http://msdn.microsoft.com/en-us/library/bb190764.aspx) 中使用 EEHeap 和 GCHandles 命令,并尝试看看是否能找到可能以这种方式泄漏/错误。
很遗憾,您可能无法获得您正在寻找的确切帮助,因为诊断这些类型的问题几乎总是非常耗时,而且在最简单的情况之外需要有人进行全面分析在转储上。基本上,不太可能有人能够指出您对堆栈溢出的直接答案。大多数人将能够向您指出可能有用的命令。您将不得不进行大量挖掘工作以了解有关正在发生的事情的更多信息。
【讨论】:
指向正确的方向就足够了。就像我说的 EEHeap 似乎只描述了整个事情的 248 MB,所以我不确定答案是否在那里。我来看看 GCHandles【参考方案5】:我最近花了一些时间诊断客户的问题,即他们的应用在终止前使用了 70GB(可能是由于达到了 IIS 应用池回收限制,但仍未得到证实)。他们给我发了一个 35 GB 的内存转储。根据我最近的经验,我可以对您提供的内容提出以下几点看法:
在 !heap -s 输出中,1.247 GB 中的 284 MB 显示在 Commit 列中。如果你在 DebugDiag 中打开这个转储,它会告诉你堆 0x60000 有 1 GB 的已提交内存。您将报告的 11 个段的提交大小加起来,发现它们加起来只有大约 102 MB,而不是 1 GB。好烦。
“丢失”的记忆并没有丢失。它实际上在 !heap -s 输出中被暗示为“虚拟块:”行。不幸的是,!heap -s 很糟糕并且没有正确显示结束地址,因此报告大小为 0。检查以下命令的输出:
!address 17e0000
!address 45bd0000
!address 6fff0000
它将报告正确的结束地址,因此会报告准确的“区域大小”。更好的是,它给出了区域大小的简洁版本。如果将这 3 个区域的大小加到 102 MB,则应该非常接近 1 GB。
那么它们里面有什么?好吧,您可以使用dq查看。通过探索,您可能会发现分配它们的原因。也许您的托管代码调用了一些具有本机端的第 3 方代码。
您可以使用!heap 6fff0000 -x -v
找到对您的堆的引用。如果有引用,您可以再次使用 !address 来查看它们所在的内存区域。在我的客户问题中,我找到了一个位于“Usage: Stack”区域的参考。 “更多信息:”提示引用了堆栈的线程,该线程恰好在顶部有一些大的 basic_string 附加/复制调用。
【讨论】:
以上是关于如何分析windbg中的<unclassified>内存使用情况的主要内容,如果未能解决你的问题,请参考以下文章
调试技巧 —— 如何利用windbg + dump + map分析程序异常