一:背景

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.exeREG DELETE "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\%ApplicationName% " /fECHO 已删除注册项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 /fECHO 已启动用户栈跟踪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 : ENABLEDHeap     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 10600000LFH 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 193a0038address 193a0038 found in_HEAP @ 10600000HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state193a0038 0501 0000  [00]   193a0050    02734 - (busy)76f377a4 ntdll!RtlpCallInterceptRoutine+0x0000002676ef61ef ntdll!RtlpAllocateHeapInternal+0x00050ddf76ea53fe ntdll!RtlAllocateHeap+0x0000003e7b81bf35 ucrtbased!heap_alloc_dbg_internal+0x000001957b81bd46 ucrtbased!heap_alloc_dbg+0x000000367b81e4ba ucrtbased!_malloc_dbg+0x0000001a7b81edd4 ucrtbased!malloc+0x000000147b7621fd Example_16_1_7!InitData+0x000010ea7b7618cc Example_16_1_7!InitData+0x000007b97b76185e Example_16_1_7!InitData+0x0000074b...

三:总结

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

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

  1. android编译非静态内部类,Android 非静态内部类/匿名类引起的内存泄漏

    一.概述 让我们先来回顾一下android内存泄漏的相关概念: 内存溢出:android系统会给每个安卓程序分配一定的内存,当程序所使用的内存超过最大值就会造成内存溢出,就是常说的OOM 内存泄漏:简 ...

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

    前言 在 使用Memory Analyzer tool(MAT)分析内存泄漏(一)中,我介绍了内存泄漏的前因后果.在本文中,将介绍MAT如何根据heap dump分析泄漏根源.由于测试范例可能过于简单 ...

  3. 解决Solaris应用程序开发内存泄漏问题

    作者: 李凌云,张一峰(laoeyu) 内存泄漏是应用软件开发过程中经常会遇到的问题,应用长期内存泄漏会占用大量操作系统内存资源,直接导致应用程序运行不稳定,严重时甚至还会影响到操作系统的正常运行.为 ...

  4. 解决Solaris应用程序开发内存泄漏问题 (1)

    作者: 李凌云,张一峰(laoeyu) 概述 内存泄漏是应用软件开发过程中经常会遇到的问题,应用长期内存泄漏会占用大量操作系统内存资源,直接导致应用程序运行不稳定,严重时甚至还会影响到操作系统的正常运 ...

  5. 利用MAT进行内存泄漏分析

    ##前言 对于程序员来说码代码容易,保证代码的稳定性很难.有时候写完一个功能可能只需要一天时间,但是这个功能隐藏的bug导致的线上问题排查可能需要一周或者更长时间.因此,拥有良好的代码结构和编码规范是 ...

  6. Android 性能优化之内存泄漏检测以及内存优化(上)

    在 Java 中,内存的分配是由程序完成的,而内存的释放则是由 Garbage Collecation(GC) 完成的,Java/Android 程序员不用像 C/C++ 程序员一样手动调用相关函数来 ...

  7. LeakCanary是如何定位内存泄漏的,看完就懂了

    文章目录 一.LeakCanary的简单使用 二.LeakCanary原理简单分析: 2-1.LeakCanary原理简述 2-2.ActivityLifecycleCallbacks使用 2-2-1 ...

  8. Android:最全面详细的性能优化攻略(含内存优化、内存泄漏、绘制优化、布局优化、图片优化、APK优化、多线程优化、列表优化等)

    前言:佛教中有一句话:初学者的心态,拥有初学者心态是件了不起的事情.真正的大师永远怀有一颗学徒的心. 一.概述 在Android中,性能优化是细分领域中最难且也是知识面涉及最深和最广的方向之一. 更快 ...

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

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

最新文章

  1. 干货 | 漫谈图神经网络
  2. JavaScript实现递归楼梯问题(带记忆的递归解决方案)算法(附完整源码)
  3. bootcmd 和bootargs
  4. com.microsoft.sqlserver.jdbc.SQLServerException: 索引 7 超出范围。
  5. LNK2019:无法解析的外部符号
  6. 2名数学家或发现史上最快超大乘法运算法,欲破解困扰人类近半个世纪的问题...
  7. PageHelper分页时超过最大数量的页数仍然返回数据,PageHelper分页失效
  8. java中outputstream以及其子类 flush有什么作用呢
  9. SQL Server - THROW字句对比RAISERROR子句
  10. 权限Permissions
  11. Linux命令之iconv命令
  12. 三步教会你在solidworks中画铝型材
  13. 《CMS后台系统》项目实战 详细分解
  14. 利用html2canvas和vue-qr生成带头像二维码的分享海报(二)
  15. build-essential unmet dependencies 有未满足依赖 解决办法
  16. 如何立即尝试macOS High Sierra Beta
  17. 最近邻搜索|Nearest neighbor search
  18. Linux C编程 itoa()函数 atoi()函数
  19. 词向量经典模型:从word2vec、glove、ELMo到BERT
  20. java小项目之贪吃蛇项目(图解超详细)

热门文章

  1. 计算机地球一小时word处理,地球一小时策划案
  2. 用vue.js实现的网页计算器源码
  3. javabean的含义_javaBean概念
  4. KDJ MACO OBV技术指标
  5. 图片处理——NDK实现拼接长图
  6. 当Google大数据遇上以太坊数据集,这会是一个区块链+大数据的成功案例吗?
  7. 2019-写给自己的话
  8. POI 设置word 行距设置为固定值
  9. windows安全机制之登陆
  10. C语言 大笨钟的心情