一:背景

上一篇我们聊到了如何用 PerfView 去侦察 NTHeap 的内存泄漏,这种内存泄漏往往是用 C 的 malloc 或者 C++ 的 new 分配而不释放所造成的,这一篇我们来聊一下由 VirtualAlloc 方法造成的泄漏如何去甄别?

了解 VirtualAlloc 的朋友肯定说, C# 这种高层语言怎么可能会用 VirtualAlloc 呢?即便是 C++ 大概率也不会用这个,其实这么说还是世面见少了,经历的案例太少,接下来我们就来简要聊一聊。

二: C# 中真的会用 VirtualAlloc 吗

常规的 C# 内存分配确实不会直接调用 VirtualAlloc,但那些图形图形的工具方法肯定会直接用的,比如说 Bitmap,如果不信的话,我可以让你眼见为实,先上一段代码。


static void Main(string[] args)
{
for (int i = 0; i < int.MaxValue; i++)
{
Test2();
Console.WriteLine(i);
} Console.ReadLine();
} public static void Test2()
{
int width = 1000;
int height = 1000; Bitmap bitmap = new Bitmap(width, height); string path = @"D:\test\1.jpg"; bitmap.Save(path);
}

这段代码中我会生成 1000x1000 的图片,接下来用 bp KernelBase!VirtualAlloc 去做一个拦截。


0:009> bp KernelBase!VirtualAlloc
0:009> g
Breakpoint 0 hit
KERNELBASE!VirtualAlloc:
00007ffd`0d53f9e0 4883ec38 sub rsp,38h
0:000> k
# Child-SP RetAddr Call Site
00 00000000`001ce828 00007ffc`eaaf4483 KERNELBASE!VirtualAlloc
01 00000000`001ce830 00007ffc`eaaf35fb gdiplus!GpMemoryBitmap::AllocBitmapData+0x137
02 00000000`001ce870 00007ffc`eaacded1 gdiplus!GpMemoryBitmap::AllocBitmapMemory+0x3f
03 00000000`001ce8b0 00007ffc`eaacddf2 gdiplus!GpMemoryBitmap::InitNewBitmap+0x49
04 00000000`001ce8f0 00007ffc`eaacdf6f gdiplus!CopyOnWriteBitmap::CopyOnWriteBitmap+0x8a
05 00000000`001ce930 00007ffc`eaace074 gdiplus!GpBitmap::GpBitmap+0x6b
06 00000000`001ce970 00007ffc`357d8143 gdiplus!GdipCreateBitmapFromScan0+0xc4
07 00000000`001ce9d0 00007ffc`357e8eb1 0x00007ffc`357d8143
08 00000000`001ceaa0 00007ffc`357d3288 System_Drawing_Common!System.Drawing.Bitmap..ctor+0x31 [_/src/libraries/System.Drawing.Common/src/System/Drawing/Bitmap.cs @ 80]
09 00000000`001ceaf0 00007ffc`357d2974 ConsoleApp7!ConsoleApp7.Program.Test2+0x58 [D:\net6\ConsoleApp1\ConsoleApp7\Program.cs @ 27]
...

从输出中可以看到在 Program.Test2调用的过程中果然被 VirtualAlloc 拦住了,而区区 200 多个 Bitmap 就已经分配了 1G 个内存,截图如下:

而此时的 GCHeap 上才区区 1.7M


0:000> !eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x0000000002941030
generation 1 starts at 0x0000000002941018
generation 2 starts at 0x0000000002941000
ephemeral segment allocation context: none
segment begin allocated committed allocated size committed size
0000000002940000 0000000002941000 0000000002ADFFE8 0000000002AE2000 0x19efe8(1699816) 0x1a1000(1708032)
Large object heap starts at 0x0000000012941000
segment begin allocated committed allocated size committed size
0000000012940000 0000000012941000 0000000012941018 0000000012942000 0x18(24) 0x1000(4096)
Pinned object heap starts at 0x000000001A941000
000000001A940000 000000001A941000 000000001A949C10 000000001A952000 0x8c10(35856) 0x11000(69632)
Total Allocated Size: Size: 0x1a7c10 (1735696) bytes.
Total Committed Size: Size: 0x1a2000 (1712128) bytes.
------------------------------
GC Allocated Heap Size: Size: 0x1a7c10 (1735696) bytes.
GC Committed Heap Size: Size: 0x1a2000 (1712128) bytes.

非常明显的非托管泄漏。

三:如何用 Perfview 检测

perfview 是一个非常好的运行时检测工具,它也是根据 钩子函数 拦截后看分配量来做一个权重,最终根据权重占比寻找到问题函数调用栈。

勾选上 VirtualAlloc 之后就可以点击 Start Collection,5s 之后就会生成一个 统计报表 ,我们点击 Memory -> Net Virtual Alloc Stacks 选项。

在弹出面板中选择我们的程序,点击 CallTree 面板,清除 GroupPats 分组,截图如下:

从面板中可以看到,在内存分配权重总量上,Test2() 占比 97.9%, 说明确实是一个问题,而且是初始化 Bitmap 出来的。

到这里, VirtualAlloc 的泄漏问题就找出来了, 如果用 WinDbg 分析的话,还需要开启 ust 选项,也是记录线程栈,但使用起来相对繁琐。

最新文章

  1. python(一)
  2. vs2012中使用Icenium开发应用
  3. [Java] Steam文件输入流
  4. 多点触摸画板(MultiTouchCanvas)
  5. Python 时间戳与时间字符串互相转
  6. IOS UIWebView截获html并修改便签内容,宽度自适应
  7. COJN 0583 800602分苹果
  8. [Android学习笔记]ShareSDK的使用
  9. Canvas rontate(旋转) 使用误区
  10. html5 canvas的教程
  11. WordPress 4.8 安装配置教程 (基于 centos 7.3, php 7.0, mysql 5.7.19, nginx 1.12.1)
  12. PHP文件头BOM头问题
  13. MinerUrl.java 解析页面后存储URL类
  14. 外观模式facade
  15. 提取Jar2Exe源代码,JavaAgent监控法
  16. java.lang.NoSuchMethodError: com.opensymphony.xwork2.config.ConfigurationManager.addConfigurationPro
  17. hdu-1421(dp)
  18. week9:个人博客作业
  19. 重构Tips
  20. HDU 4717 The Moving Points (三分)

热门文章

  1. 3000帧动画图解MySQL为什么需要binlog、redo log和undo log
  2. 数据分析工具Metabase--Metabase安装(最详细的安装教程)
  3. Koa系框架(egg/cabloy)如何获取微信支付回调请求中的xml参数
  4. Camunda开源流程引擎快速入门——Hello World
  5. .NET中测试代码运行时间
  6. 使用AndroidKiller进行APK反编译
  7. CANN算子:利用迭代器高效实现Tensor数据切割分块处理
  8. Nacos 的安装与服务的注册
  9. Java数组和Arrays 类
  10. 数据结构-二叉树(Binary Tree)