PerfView专题 (第三篇):如何寻找 C# 中的 VirtualAlloc 内存泄漏
一:背景
上一篇我们聊到了如何用 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 选项,也是记录线程栈,但使用起来相对繁琐。
PerfView专题 (第三篇):如何寻找 C# 中的 VirtualAlloc 内存泄漏的更多相关文章
- PerfView专题 (第十一篇):使用 Diff 功能洞察 C# 内存泄漏增量
一:背景 去年 GC架构师 Maoni 在 (2021 .NET 开发者大会) [https://ke.segmentfault.com/course/1650000041122988/section ...
- PerfView专题 (第四篇):如何寻找 C# 中程序集泄漏
一:背景 前两篇我们都聊到了非托管内存泄漏,一个是 HeapAlloc ,一个是 VirtualAlloc,除了这两种泄漏之外还存在其他渠道的内存泄漏,比如程序集泄漏,这一篇我们就来聊一聊. 二: 程 ...
- PerfView专题 (第五篇):如何寻找 C# 托管内存泄漏
一:背景 前几篇我们聊的都是 非托管内存泄漏,这一篇我们再看下如何用 PerfView 来排查 托管内存泄漏 ,其实 托管内存泄漏 比较好排查,尤其是用 WinDbg,毕竟C#是带有丰富的元数据,不像 ...
- PerfView专题 (第八篇):洞察 C# 内存泄漏之寻找静态变量名和GC模式
一:背景 这篇我们来聊一下 PerfView 在协助 WinDbg 分析 Dump 过程中的两个超实用技巧,可能会帮助我们快速定位最后的问题,主要有如下两块: 洞察内存泄漏中的静态大集合变量名. 验证 ...
- PerfView专题 (第十篇):洞察 C# 终结队列引发的内存泄漏
一:背景 C# 程序内存泄漏的诱发因素有很多,但从顶层原理上来说,就是该销毁的 用户根 对象没有被销毁,从而导致内存中意料之外的对象无限堆积,导致内存暴涨,最终崩溃,这其中的一个用户根就是 终结器队列 ...
- PerfView专题 (第六篇):如何洞察 C# 中 GC 的变化
一:背景 在洞察 GC 方面,我觉得市面上没有任何一款工具可以和 PerfView 相提并论,这也是为什么我会在 WinDbg 之外还要学习这么一款工具的原因,这篇我们先简单聊聊 PerfView 到 ...
- PerfView专题 (第七篇):如何洞察触发 GC 的 C# 代码?
一:背景 上一篇我们聊到了如何用 PerfView 洞察 GC 的变化,但总感觉还缺了点什么? 对,就是要跟踪到底是什么代码触发了 GC,这对我们分析由于 GC 导致的 CPU 爆高有非常大的参考价值 ...
- asp.net signalR 专题—— 第三篇 如何从外部线程访问 PersistentConnection
在前面的两篇文章中,我们讲到的都是如何将消息从server推向client,又或者是client再推向server,貌似这样的逻辑没什么异常,但是放在真实 的环境中,你会很快发现有一个新需求,如何根据 ...
- python学习之【第三篇】:Python中的字符串及其所具有的方法
1.前言 字符串str是Python中最常用的数据类型.我们可以使用单引号''或双引号""包裹一段字符来创建字符串. 2.字符串创建 str1 = 'hello world' st ...
随机推荐
- 图解 Apache SkyWalking UI 的使用
Apache SkyWalking的UI界面主要分为以下几个区域: 功能选择区:这里列出了主要的UI功能,包括仪表盘.拓扑图.追踪.性能刨析.告警等功能 重新加载区:控制重新加载机制,包括定期重新加载 ...
- DYOJ 【20220303模拟赛】最少分组 题解
最少分组 题意 \(n\) 个点 \(m\) 条边的无向图,可以删掉 0 条或多条边,求满足条件的最小连通块数量: 对每个顶点对 \((a,b)\) ,若 \(a\) 和 \(b\) 同属于一个连通块 ...
- tf.data(二) —— 并行化 tf.data.Dataset 生成器
在处理大规模数据时,数据无法全部载入内存,我们通常用两个选项 使用tfrecords 使用 tf.data.Dataset.from_generator() tfrecords的并行化使用前文已经有过 ...
- 轻量级多级菜单控制框架程序(C语言)
1.前言 作为嵌入式软件开发,可能经常会使用命令行或者显示屏等设备实现人机交互的功能,功能中通常情况都包含 UI 菜单设计:很多开发人员都会有自己的菜单框架模块,防止重复造轮子,网上有很多这种菜单框架 ...
- 编写一个kubernetes controller
Overview 根据Kuberneter文档对Controller的描述,Controller在kubernetes中是负责协调的组件,根据设计模式可知,controller会不断的你的对象(如Po ...
- 16.Nginx优化与防盗链
Nginx优化与防盗链 目录 Nginx优化与防盗链 隐藏版本号 修改用户与组 缓存时间 日志切割 小知识 连接超时 更改进程数 配置网页压缩 配置防盗链 配置防盗链 隐藏版本号 可以使用 Fiddl ...
- 基于slate构建文档编辑器
基于slate构建文档编辑器 slate.js是一个完全可定制的框架,用于构建富文本编辑器,在这里我们使用slate.js构建专注于文档编辑的富文本编辑器. 描述 Github | Editor DE ...
- kvm虚拟机在线扩容
fdisk -l查看当前虚拟机磁盘容量 1. 镜像扩容 先操作镜像,给镜像增加2T容量: 关闭虚拟机back_log,然后再宿主机上给虚拟机扩容 qemu-img info /home/kvm/bac ...
- RPA应用场景-考勤审批
场景概述 考勤审批 所涉系统名称 考勤系统,微信 人工操作(时间/次) 5分钟 所涉人工数量 43 操作频率 不定时 场景流程 1.客户领导长期出差,又不想对考勤系统做深度开发: 2.员工请假后,领导 ...
- 宝塔Linux面板安装教程
宝塔Linux面板安装教程 安装要求: 内存:512M以上,推荐768M以上(纯面板约占系统60M内存) 硬盘:300M以上可用硬盘空间(纯面板约占20M磁盘空间) 系统:CentOS 7.1+ (U ...