一:背景
去年 GC架构师 Maoni
在 (2021 .NET 开发者大会)
[https://ke.segmentfault.com/course/1650000041122988/section/1500000041123017] 上演示过 PerfView 的 Diff 功能来寻找内存增量,个人感觉这个功能非常不错,简单省事,所以这里就整合到 PerfView 专题中,分享一下给大家。
二:洞察内存增量
1. 什么是内存增量
其实非常好理解,就是当你的程序出现了内存泄漏,你可以在程序内存增长的过程中截取两个 dump 文件,然后通过 PerfView 观察其中的内存增量是什么?帮助我们快速找出可能被泄漏的对象。
当然你用 WinDBG 的话也是没有问题的,只不过需要用肉眼扫一下而已,接下来举两个例子说明一下。
2. 静态集合的内存泄漏
很多 dump 的内存泄漏,源自于里面的某一个 static 变量无限堆积所致,为了方便说明,先上一段测试代码。
internal class Program
{
static void Main(string[] args)
{
Task.Run(RunTest);
Console.ReadLine();
}
public static List<string> my_big_list = new List<string>();
static void RunTest()
{
for (int i = 0; i < 50000; i++)
{
my_big_list.Add(string.Join(",", Enumerable.Range(0, 10000)));
Console.WriteLine(i);
}
}
}
接下来在程序的运行过程中,我们分别截取两个 dump 文件, 点击菜单栏的 Memory -> Take Heap Snapshot
按钮,在 Filter
中搜索需要采集的进程,然后点击 Dump GC Heap
即可,参考如下图:
![ff3a847893e266f1fccc848a3109af8a.png](https://i-blog.csdnimg.cn/blog_migrate/901ab87b0466389729428d4e0fcebd72.png)
稍等片刻,你就会看到两个 gcdump
文件,这个和普通的 dump
是不一样的,算是 PerfView 专用的轻量级 dump 文件,截图如下:
![aeb4c162a35ac508adf03738ae47a8cb.png](https://i-blog.csdnimg.cn/blog_migrate/2d152b6ccf64513085d4c82257af74c9.png)
接下来点击两个 gcdump 中的 Heap Stacks
,对比 inc%
列后发现,内存都被一个叫 my_big_list
变量给吃掉了,前者的count为 10509
, 后者是 15172
,截图如下:
![36977f5c6e78da49fa0f28bff2a35b6e.png](https://i-blog.csdnimg.cn/blog_migrate/6996473da18ef83768e45653073964c2.png)
虽然肉眼可以简单观察,但这里可以使用专业的 Diff
功能,让 PerfView 帮我洞察 栈 的总体增量差异,点击菜单栏中的 Diff -> With Baseline: Heap Stacks [.....]
按钮,即让本 gcdump
和另一个 gcdump
做比较,截图如下:
![c732277b2a0603fce6b104bb61fd635d.png](https://i-blog.csdnimg.cn/blog_migrate/bae2b1031c8869f999c8d15a517eeaeb.png)
不过要注意的是,这两个窗口一定要打开,这个是比较坑的,哈哈,接下来就会看到如下图:
![6aa08f4d159a8ae6099fc4837ed67e9b.png](https://i-blog.csdnimg.cn/blog_migrate/a68c1155d39a438fd8ee90d9d4d8fa3b.png)
从图中可以清晰的看到,这两个 dump 的增量主要来自于 my_big_list
集合,往细处说就是 string 增长了 4663
个。
3. 事件event泄漏
我们再看一个事件泄漏的例子,参考如下代码:
// event 泄漏
class Program
{
static event Action TestEvent;
static void Main(string[] args)
{
var memory = new TestAction();
//handle 泄漏
for (int i = 0; i < int.MaxValue; i++)
{
TestEvent += memory.Run;
if (i % 500 == 0)
{
Console.WriteLine(i);
}
}
Console.ReadLine();
}
public static void OnTestEvent()
{
if (TestEvent != null)
{
TestEvent();
}
else
{
Console.WriteLine("Test Event is null");
}
}
class TestAction
{
public void Run()
{
Console.WriteLine("TestAction Run.");
}
}
}
将程序运行起来,用 Process Explorer
抓两个 dump 文件下来,然后点击 Memory -> Take Heap Snapshot From Dump
按钮,截图如下:
![b69da847530a61ba9c692c5fef415a6b.png](https://i-blog.csdnimg.cn/blog_migrate/ec7d54b9604addef795e8487fc4b1519.png)
在弹出的对话框中设置需要提取的 dump 文件,稍等片刻就会生成如下两个 gcdump
文件,截图如下:
![2d11c8b4c1fcac19c0c7c4165ec91483.png](https://i-blog.csdnimg.cn/blog_migrate/3692a8913547daae85ce6ee0fa24b10c.png)
接下来将两个 gcdump 都打开,发现内存都被程序中的一个叫 TestEvent
占用了,如下图所示:
![0220b9317c7c5ebf2667034101c2998e.png](https://i-blog.csdnimg.cn/blog_migrate/36e52f64e50383061c4a0c98eda48d8b.png)
接下来就可以使用 Diff
对比功能了,可以观察到,TestEvent 下面的 Action 增量了将近 700w
个,截图如下:
![bb7df51c74a5e0ed088643cd3c653137.png](https://i-blog.csdnimg.cn/blog_migrate/a30f4382ac4fd59a88c39ddfb2e1bb99.png)
这里稍微说一下,为什么会增量 700w 的 Action,这主要是因为 event 是一个多播委托,内部有一个 Action 集合,也正是这个 Action 集合
在无限膨胀。