一:背景

1. 背景

前段时间有位朋友咨询说他的程序出现了非托管内存泄漏,说里面有很多的 HEAP_BLOCK 都被标记成了 Internal 状态,而且 size 都很大, 让我帮忙看下怎么回事? 比如下面这样。


1cbea000: 42000 . 42000 [101] - busy (41fe8) Internal
1cc2c000: 42000 . 42000 [101] - busy (41fe8) Internal
1cc6e000: 42000 . 42000 [101] - busy (41fe8) Internal
1ccb0000: 42000 . 42000 [101] - busy (41fe8) Internal
1ccf2000: 42000 . 42000 [101] - busy (41fe8) Internal
1cd34000: 42000 . 42000 [101] - busy (41fe8) Internal
1cd76000: 42000 . 42000 [101] - busy (41fe8) Internal
1cdb8000: 42000 . 42000 [101] - busy (41fe8) Internal
1cdfa000: 42000 . 42000 [101] - busy (41fe8) Internal
1ce3c000: 42000 . 42000 [101] - busy (41fe8) Internal

其实这个涉及到了 NTHeap 的一些基础知识。

二:原理浅析

1. NTHeap 分配架构图

千言万语不及一张图。

从图中可以清晰的看到,当 Heap_Entry 标记了 Internel ,其实是给 前段堆 LFH 做内部存储用的,当然这里的大块内存是按有序的 segmentblock 切分,相当于堆中堆

接下来我们验证下这个说法到底对不对? 写一个测试程序,让其在 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.exe REG 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 /f
REG ADD "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\%ApplicationName%" /v StackTraceDatabaseSizeInMb /t REG_DWORD /d 0x00000400 /f ECHO 已启动用户栈跟踪 PAUSE

把程序跑起来,然后抓一个 dump 文件。

三:WinDbg 分析 Internel

1. 内存都去了哪里


0:000> !address -summary --- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
Free 70 e1292000 ( 3.518 GB) 87.95%
<unknown> 138 c42f000 ( 196.184 MB) 39.76% 4.79%
Other 11 805d000 ( 128.363 MB) 26.02% 3.13%
Heap 832 6f55000 ( 111.332 MB) 22.57% 2.72%
Image 280 3061000 ( 48.379 MB) 9.81% 1.18%
Stack 27 900000 ( 9.000 MB) 1.82% 0.22%
TEB 9 19000 ( 100.000 kB) 0.02% 0.00%
PEB 1 3000 ( 12.000 kB) 0.00% 0.00% --- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_FREE 70 e1292000 ( 3.518 GB) 87.95%
MEM_RESERVE 94 14830000 ( 328.188 MB) 66.52% 8.01%
MEM_COMMIT 1204 a52e000 ( 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 traces
LFH Key : 0x38843509
Termination on corruption : ENABLED
Heap Flags Reserv Commit Virt Free List UCR Virt Lock Fast
(k) (k) (k) (k) length blocks cont. heap
-----------------------------------------------------------------------------
10600000 08000002 113704 107896 113492 1679 72 11 0 6 LFH
10560000 08001002 60 16 60 3 2 1 0 0
10a70000 08001002 60 16 60 2 2 1 0 0
12450000 08001002 60 4 60 0 1 1 0 0
123b0000 08041002 60 4 60 2 1 1 0 0
15ef0000 08041002 60 4 60 0 1 1 0 0
-----------------------------------------------------------------------------

从卦中可知,当前内存都是 Heap 给吃掉了,往细处说就是 10600000 这个进程堆,接下来使用 !heap -h 10600000 把堆上的 segment 和 block 都显示出来。

从图中可以看到,全是这种 Internel 的标记,而且 request size = 41fe8 = 270312 byte= 263k,很显然我并没有做 27w byte 的内存分配,那这些源自于哪里呢?

2. 源自于哪里?

因为 前段堆 相当于堆中堆,所以我们观察下有没有开启LFH,有两种方法。

  1. 观察 !heap -s 命令输出的 Fast heap 列是不是带有 LFH ?

  2. 观察 HEAPFrontEndHeap 字段是否为 null ?


0:000> dt nt!_HEAP 10600000
ntdll!_HEAP
+0x0e4 FrontEndHeap : 0x10570000 Void
+0x0e8 FrontHeapLockCount : 0
...

接下来就是怎么把 FrontEndHeap 中的信息给导出来? 你完全可以根据这个首地址一步步的导出,也可以使用强大的 heap 扩展命令 -hl , 这里的 l 就是 LFH 的意思。


0:000> !heap -hl 10600000
LFH data region at 193a0018 (subsegment 106e4a30):
193a0038: 02808 - busy (2734)
193a2840: 02808 - busy (2734)
193a5048: 02808 - busy (2734)
193a7850: 02808 - busy (2734)
193aa058: 02808 - busy (2734)
193ac860: 02808 - busy (2734)
193af068: 02808 - busy (2734)
193b1870: 02808 - busy (2734)
...
LFH data region at 1cf02018 (subsegment 10695888):
1cf02038: 02808 - busy (2734)
1cf04840: 02808 - busy (2734)
1cf07048: 02808 - busy (2734)
1cf09850: 02808 - busy (2734)
1cf0c058: 02808 - busy (2734)
...

可以看到有大量的 alloc = 02808 = 10248 byte 大小的 block ,而且还有很多的 subsegment 字样,也说明了 Internel 的组成结构,由于记录了 ust,我们就可以使用 !heap -p -a 把这个block的调用栈给找出来。


0:000> !heap -p -a 193a0038
address 193a0038 found in
_HEAP @ 10600000
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
193a0038 0501 0000 [00] 193a0050 02734 - (busy)
76f377a4 ntdll!RtlpCallInterceptRoutine+0x00000026
76ef61ef ntdll!RtlpAllocateHeapInternal+0x00050ddf
76ea53fe ntdll!RtlAllocateHeap+0x0000003e
7b81bf35 ucrtbased!heap_alloc_dbg_internal+0x00000195
7b81bd46 ucrtbased!heap_alloc_dbg+0x00000036
7b81e4ba ucrtbased!_malloc_dbg+0x0000001a
7b81edd4 ucrtbased!malloc+0x00000014
7b7621fd Example_16_1_7!InitData+0x000010ea
7b7618cc Example_16_1_7!InitData+0x000007b9
7b76185e Example_16_1_7!InitData+0x0000074b
...

三:总结

本篇主要是解析了 Internel 标记的可能来源地,没有对 LFH 做进一步的讲解,更多的 NtHeap 知识可以参考 《深入解析 Windows 操作系统》 一书。

C# 内存泄漏之 Internal 关键词代表什么?的更多相关文章

  1. 使用Memory Analyzer tool(MAT)分析内存泄漏(二)

    转载自:http://www.blogjava.net/rosen/archive/2010/06/13/323522.html 前言的前言 写blog就是好,在大前提下可以想说什么写什么,不像投稿那 ...

  2. 使用Memory Analyzer tool(MAT)分析内存泄漏

    前言的前言 写blog就是好,在大前提下可以想说什么写什么,不像投稿那么字字斟酌.上周末回了趟成都办事,所以本文来迟了.K117从达州经由达成线往成都方向走的时候,发现铁路边有条河,尽管我现在也不知道 ...

  3. (转)从内存管 理、内存泄漏、内存回收探讨C++内存管理

    http://www.cr173.com/html/18898_all.html 内存管理是C++最令人切齿痛恨的问题,也是C++最有争议的问题,C++高手从中获得了更好的性能,更大的自由,C++菜鸟 ...

  4. 性能监控 | MAT分析内存泄漏

    使用MAT分析内存泄漏(二)八周年重印版 - 知乎 .u-safeAreaInset-top { height: constant(safe-area-inset-top) !important; h ...

  5. PerfView专题 (第八篇):洞察 C# 内存泄漏之寻找静态变量名和GC模式

    一:背景 这篇我们来聊一下 PerfView 在协助 WinDbg 分析 Dump 过程中的两个超实用技巧,可能会帮助我们快速定位最后的问题,主要有如下两块: 洞察内存泄漏中的静态大集合变量名. 验证 ...

  6. 为什么各大厂自研的内存泄漏检测框架都要参考 LeakCanary?因为它是真强啊!

    请点赞关注,你的支持对我意义重大. Hi,我是小彭.本文已收录到 GitHub · AndroidFamily 中.这里有 Android 进阶成长知识体系,有志同道合的朋友,关注公众号 [彭旭锐] ...

  7. PerfView专题 (第十篇):洞察 C# 终结队列引发的内存泄漏

    一:背景 C# 程序内存泄漏的诱发因素有很多,但从顶层原理上来说,就是该销毁的 用户根 对象没有被销毁,从而导致内存中意料之外的对象无限堆积,导致内存暴涨,最终崩溃,这其中的一个用户根就是 终结器队列 ...

  8. 使用 Android Studio 检测内存泄漏与解决内存泄漏问题

    本文在腾讯技术推文上 修改 发布. http://wetest.qq.com/lab/view/63.html?from=ads_test2_qqtips&sessionUserType=BF ...

  9. .net中事件引起的内存泄漏分析

    系列主题:基于消息的软件架构模型演变 在Winform和Asp.net时代,事件被大量的应用在UI和后台交互的代码中.看下面的代码: private void BindEvent() { var bt ...

随机推荐

  1. typescript关于postgres数据库的API封装

    文章结构     1 新建 postgres.ts 文件     2 配置文件说明     3 依赖引用说明     4 API使用示例   1 新建 postgres.ts 文件,代码如下: imp ...

  2. stringstrean类中关于clear和str的比较

    stringstream类涉及到多次类型转换的时候容易出现异常错误 因为第一次数据如果读入eof或者输出完整来到eof,此时stringstream会自动为其添上eofbit标志位,此时继续进行任何操 ...

  3. jsx/tsx使用cssModule和typescript-plugin-css-modules

    目录 1,前言 2,效果图 3,如何使用 3.1,安装 3.2,配置 4,示例 5,插件错误处理 5.1,错误触发原因 5.2,解决办法 1,前言 在vite/webpack搭建的项目中,不管是vue ...

  4. Vue ref属性 && props配置项

    1 // # ref属性: 2 // # 1.用来给元素或者子组件注册引用信息(id的替代者) 3 // # 2.应用在html标签上获取的是真实的DOM元素,应用在组件标签上是组件实例对象(vc) ...

  5. [算法1-排序](.NET源码学习)& LINQ & Lambda

    [算法1-排序](.NET源码学习)& LINQ & Lambda 说起排序算法,在日常实际开发中我们基本不在意这些事情,有API不用不是没事找事嘛.但必要的基础还是需要了解掌握. 排 ...

  6. which 和 that 在定语从句中作介词宾语的用法

    关系代词在定语从句中作介词的宾语,且介词在关系代词之前时,关系代词应该用 which:介词在定语从句句末,关系代词可以用 that 或 which. (一)He teaches in a school ...

  7. 分布式id生成器,雪花算法IdWorker

    /** * <p>名称:IdWorker.java</p> * <p>描述:分布式自增长ID</p> * <pre> * Twitter的 ...

  8. SiteSucker Pro for Mac 专业的网站下载工具

    SiteSucker Mac版是Mac os平台上的一款帮助用户下载数据的mac下载工具,SiteSucker绝对是一扒网站的利器,不仅仅是下载网站的HTML源文件,他连网站整体架构以及下面的所有文本 ...

  9. 论文翻译:2021_A New Real-Time Noise Suppression Algorithm for Far-Field Speech Communication Based on Recurrent Neural Network

    论文地址:一种新的基于循环神经网络的远场语音通信实时噪声抑制算法 引用格式:Chen B, Zhou Y, Ma Y, et al. A New Real-Time Noise Suppression ...

  10. java基础———标识符和关键字

    标识符以字母开头  (A-Z)或(a-z)    美元符($)     下划线(_) 不能以关键字作为变量名或者方法名 标识符大小写不能混淆 可以中文(不建议) 常用的关键字