Windbg下.NET进程堆内存与对象总内存不匹配的排查疑问
.NET内存泄漏排查:!address -summary与!dumpheap -stat内存差异分析
问题背景
调试.NET进程排查内存泄漏时,执行!address -summary得到如下结果:
0:000> !address -summary --- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal Free 648 7dbb`1f7df000 ( 125.731 TB) 98.23% <unknown> 7417 241`d1e9c000 ( 2.257 TB) 99.47% 1.76% Heap 1304 2`e58f8000 ( 11.587 GB) 0.50% 0.01% Image 2938 0`22f13000 ( 559.074 MB) 0.02% 0.00% Stack 195 0`05f00000 ( 95.000 MB) 0.00% 0.00% Other 30 0`001e7000 ( 1.902 MB) 0.00% 0.00% TEB 65 0`00082000 ( 520.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_MAPPED 5588 200`16897000 ( 2.000 TB) 88.16% 1.56% MEM_PRIVATE 3424 44`a7067000 ( 274.610 GB) 11.82% 0.21% MEM_IMAGE 2938 0`22f13000 ( 559.074 MB) 0.02% 0.00% --- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal MEM_FREE 648 7dbb`1f7df000 ( 125.731 TB) 98.23% MEM_RESERVE 2348 241`9ef5b000 ( 2.256 TB) 99.44% 1.76% MEM_COMMIT 9602 3`418b6000 ( 13.024 GB) 0.56% 0.01%
其中Heap总大小为11.587 GB,但执行!dumpheap -stat后,所有托管对象总内存仅约286 MB,远小于前者,需明确剩余内存去向及排查方法。
差异原因
!address -summary中的Heap是Windows系统层面的堆范畴,包含:
- .NET托管堆的保留/提交内存
- 进程中所有非托管堆(如C++堆、Win32 API分配的堆)
- CLR内部使用的非托管内存
- 堆内存碎片
而!dumpheap -stat仅统计托管堆中已分配的托管对象内存,两者统计范围完全不同,因此出现巨大差异。
剩余内存的可能去向及排查方法
1. 托管堆的保留但未提交内存
CLR会预先保留大块内存区域(MEM_RESERVE状态),仅在实际需要时提交(MEM_COMMIT),!dumpheap不会统计未提交的保留内存。
- 排查命令:
该命令会输出每个托管堆段的!eeheap -gcReserved和Committed大小,对比两者差值即可得到未提交的保留内存总量。
2. 非托管内存占用
非托管堆的内存分配完全脱离.NET托管系统,不会被!dumpheap统计,但属于!address的Heap范畴。
- 排查步骤:
- 查看所有非托管堆的摘要,定位占用较大的堆:
!heap -s - 对目标堆执行统计分析,查看内存分配分布:
(替换!heap -stat -h <堆Handle><堆Handle>为步骤1中找到的大堆句柄,如0x0000000000180000) - 过滤大尺寸分配记录,进一步查看调用栈:
!heap -flt s <大小阈值> # 例如过滤大于1MB的分配:!heap -flt s 1048576 !heap -p -a <分配地址> # 查看指定分配地址的调用栈详情
- 查看所有非托管堆的摘要,定位占用较大的堆:
3. CLR内部的非托管内存
CLR自身运行会占用部分非托管内存(如JIT编译缓存、GC内部结构、线程本地存储等),这些内存属于Heap范畴但不被!dumpheap统计。
- 排查命令:
- 查看CLR加载器的内存使用:
!eeheap -loader - 查看线程相关的内存占用:
!eeheap -threads
- 查看CLR加载器的内存使用:
4. 堆内存碎片
托管堆或非托管堆中的内存碎片(已分配块之间的空闲空间)属于Heap的已提交部分,但未被对象占用,因此不会被!dumpheap统计。
- 排查方法:
- 托管堆碎片:执行
!dumpheap -type Free查看托管堆中的空闲块分布 - 非托管堆碎片:通过
!heap -stat输出的Fragmentation字段判断碎片程度
- 托管堆碎片:执行
整体排查流程
- 用
!eeheap -gc确认托管堆保留/提交差异,排除未提交内存的影响 - 用
!heap -s定位占用大的非托管堆,深入分析非托管分配 - 用
!eeheap -loader和!eeheap -threads检查CLR内部非托管内存 - 检查托管/非托管堆的内存碎片情况
内容的提问来源于stack exchange,提问作者Phiber
相关产品推荐
相关产品推荐

