C# 内存泄漏之 Internal 关键词代表什么?

一:背景1. 背景前段时间有位朋友咨询说他的程序出现了非托管内存泄漏,说里面有很多的 HEAP_BLOCK 都被标记成了 Internal 状态,而且 size 都很大 ,  让我帮忙看下怎么回事? 比如下面这样 。
1cbea000: 42000 . 42000 [101] - busy (41fe8) Internal1cc2c000: 42000 . 42000 [101] - busy (41fe8) Internal1cc6e000: 42000 . 42000 [101] - busy (41fe8) Internal1ccb0000: 42000 . 42000 [101] - busy (41fe8) Internal1ccf2000: 42000 . 42000 [101] - busy (41fe8) Internal1cd34000: 42000 . 42000 [101] - busy (41fe8) Internal1cd76000: 42000 . 42000 [101] - busy (41fe8) Internal1cdb8000: 42000 . 42000 [101] - busy (41fe8) Internal1cdfa000: 42000 . 42000 [101] - busy (41fe8) Internal1ce3c000: 42000 . 42000 [101] - busy (41fe8) Internal 其实这个涉及到了 NTHeap 的一些基础知识 。
二:原理浅析1. NTHeap 分配架构图千言万语不及一张图 。

C# 内存泄漏之 Internal 关键词代表什么?

文章插图
从图中可以清晰的看到,当 Heap_Entry 标记了Internel ,其实是给 前段堆 LFH 做内部存储用的,当然这里的大块内存是按有序的 segmentblock 切分,相当于堆中堆
【C# 内存泄漏之 Internal 关键词代表什么?】接下来我们验证下这个说法到底对不对? 写一个测试程序,让其在 NTHeap 上生成大量的 Internel
2. 案例演示首先来一段 C++ 代码,根据 len 参数来分配 char[] 数组大小 。
#include "iostream"#include <Windows.h>using namespace std;extern "C"{ _declspec(dllexport) int__stdcall InitData(int len);}int __stdcall InitData(int len) { char* c = new char[len]; return 1;}熟悉 C++ 的朋友一眼就能看出会存在内存泄露的情况,因为 c 没有进行 delete[]
接下来将 InitData 引入到 C# 上,代码如下:
internal class Program{[DllImport("Example_16_1_7", CallingConvention = CallingConvention.StdCall)]private static extern int InitData(int len);static void Main(string[] args){var task = Task.Factory.StartNew(() =>{for (int i = 0; i < 10000; i++){InitData(10000);Console.WriteLine($"i={i} 次操作!");}});Console.ReadLine();}}从代码中可以看到,我做了 1w 次的分配 , 而且 len=1w , 即 1wbyte , 高频且固定,这完全符合进入 LFH 堆的特性 。
为了能够记录 block 是谁分配的,在注册表中配置一个 GlobalFlag 项 。
SET ApplicationName=Example_16_1_6.exeREG DELETE "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\%ApplicationName% " /f ECHO 已删除注册项REG ADD "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\%ApplicationName%" /v GlobalFlag/t REG_SZ/d 0x00001000 /fREG ADD "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\%ApplicationName%" /v StackTraceDatabaseSizeInMb/t REG_DWORD/d 0x00000400 /fECHO 已启动用户栈跟踪PAUSE 把程序跑起来,然后抓一个 dump 文件 。
三:WinDbg 分析 Internel1. 内存都去了哪里0:000> !address -summary--- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotalFree70e1292000 (3.518 GB)87.95%<unknown>138c42f000 ( 196.184 MB)39.76%4.79%Other11805d000 ( 128.363 MB)26.02%3.13%Heap8326f55000 ( 111.332 MB)22.57%2.72%Image2803061000 (48.379 MB)9.81%1.18%Stack27900000 (9.000 MB)1.82%0.22%TEB919000 ( 100.000 kB)0.02%0.00%PEB13000 (12.000 kB)0.00%0.00%--- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotalMEM_FREE70e1292000 (3.518 GB)87.95%MEM_RESERVE9414830000 ( 328.188 MB)66.52%8.01%MEM_COMMIT1204a52e000 ( 165.180 MB)33.48%4.03%0:000> !heap -s************************************************************************************************************************NT HEAP STATS BELOW************************************************************************************************************************NtGlobalFlag enables following debugging aids for new heaps:stack back tracesLFH Key: 0x38843509Termination on corruption : ENABLEDHeapFlagsReservCommitVirtFreeListUCRVirtLockFast(k)(k)(k)(k) lengthblocks cont. heap-----------------------------------------------------------------------------10600000 08000002113704 107896 1134921679721106LFH10560000 080010026016603210010a70000 080010026016602210012450000 080010026046001100123b0000 08041002604602110015ef0000 080410026046001100-----------------------------------------------------------------------------从卦中可知 , 当前内存都是 Heap 给吃掉了,往细处说就是 10600000 这个进程堆,接下来使用 !heap -h 10600000

推荐阅读