赞
踩
上一篇我们聊到了如何用 PerfView 去侦察 NTHeap 的内存泄漏,这种内存泄漏往往是用 C 的 malloc
或者 C++ 的 new
分配而不释放所造成的,这一篇我们来聊一下由 VirtualAlloc
方法造成的泄漏如何去甄别?
了解 VirtualAlloc
的朋友肯定说, C# 这种高层语言怎么可能会用 VirtualAlloc
呢?即便是 C++
大概率也不会用这个,其实这么说还是世面见少了,经历的案例太少,接下来我们就来简要聊一聊。
常规的 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 是一个非常好的运行时检测工具,它也是根据 钩子函数 拦截后看分配量来做一个权重,最终根据权重占比寻找到问题函数调用栈。
勾选上 VirtualAlloc
之后就可以点击 Start Collection
,5s 之后就会生成一个 统计报表
,我们点击 Memory -> Net Virtual Alloc Stacks
选项。
在弹出面板中选择我们的程序,点击 CallTree
面板,清除 GroupPats
分组,截图如下:
从面板中可以看到,在内存分配权重总量上,Test2()
占比 97.9%, 说明确实是一个问题,而且是初始化 Bitmap
出来的。
到这里, VirtualAlloc 的泄漏问题就找出来了, 如果用 WinDbg 分析的话,还需要开启 ust 选项,也是记录线程栈,但使用起来相对繁琐。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。