一:背景

前几篇我们聊的都是 非托管内存泄漏,这一篇我们再看下如何用 PerfView 来排查 托管内存泄漏 ,其实 托管内存泄漏 比较好排查,尤其是用 WinDbg,毕竟C#是带有丰富的元数据,不像C++下去就是二进制。

二:如何分析

PerfView 用的是权重占比来寻找可疑的问题函数,为了方便讲述,我们先上一段问题代码。


internal class Program
{
static void Main(string[] args)
{
Task.Run(Alloc1);
Task.Run(Alloc2);
Task.Run(Alloc3); Console.ReadLine();
} static void Alloc1()
{
var list = new List<string>(); for (int i = 0; i < 200000; i++)
{
list.Add(string.Join(",", Enumerable.Range(0, 1000)));
} Console.WriteLine("Alloc1 处理完毕");
} static void Alloc2()
{
var list = new List<string>(); for (int i = 0; i < 100; i++)
{
list.Add(string.Join(",", Enumerable.Range(0, 1000)));
} Console.WriteLine("Alloc2 处理完毕");
} static void Alloc3()
{
var list = new List<string>(); for (int i = 0; i < 100; i++)
{
list.Add(string.Join(",", Enumerable.Range(0, 1000)));
} Console.WriteLine("Alloc3 处理完毕");
}
}

这段代码运行完成后会发现内存占用高达 1.5G,如下图所示:

在真实场景中,你根本不知道是谁占用了这么大的内存,在分析武器库中,用 WinDbg 肯定是最稳的,既然是介绍 PerfView 工具,得用它来分析。

二:PerfView 分析

1. 到底是哪里的泄漏

分析之前,还是要先搞清楚到底是哪里的泄漏,才好用 PerfView 追查下来,首先用 !eeheap -gc 查看下托管堆的占用大小。


0:005> !eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x0000000072D7AEC0
generation 1 starts at 0x0000000072B1B790
generation 2 starts at 0x0000000002841000
ephemeral segment allocation context: none
segment begin allocated committed allocated size committed size
0000000002840000 0000000002841000 000000001283FB10 0000000012840000 0xfffeb10(268430096) 0xffff000(268431360)
0000000023E80000 0000000023E81000 0000000033E7F0A8 0000000033E80000 0xfffe0a8(268427432) 0xffff000(268431360)
00000000347D0000 00000000347D1000 00000000447CFA98 00000000447D0000 0xfffea98(268429976) 0xffff000(268431360)
0000000045A60000 0000000045A61000 0000000055A5E2A0 0000000055A60000 0xfffd2a0(268423840) 0xffff000(268431360)
0000000055A60000 0000000055A61000 0000000065A5F7B8 0000000065A60000 0xfffe7b8(268429240) 0xffff000(268431360)
0000000065A60000 0000000065A61000 0000000073252ED8 00000000735F6000 0xd7f1ed8(226434776) 0xdb95000(230248448)
Large object heap starts at 0x0000000012841000
segment begin allocated committed allocated size committed size
0000000012840000 0000000012841000 0000000012C21130 0000000012C22000 0x3e0130(4063536) 0x3e1000(4067328)
Pinned object heap starts at 0x000000001A841000
000000001A840000 000000001A841000 000000001A845C38 000000001A852000 0x4c38(19512) 0x11000(69632)
Total Allocated Size: Size: 0x5dbcdce8 (1572658408) bytes.
Total Committed Size: Size: 0x5df71000 (1576472576) bytes.
------------------------------
GC Allocated Heap Size: Size: 0x5dbcdce8 (1572658408) bytes.
GC Committed Heap Size: Size: 0x5df71000 (1576472576) bytes.

从输出中可以看到,当前的 托管堆 占用 1.5G, 这就说明当前的泄漏确实是 托管堆 的泄漏,这就给继续分析指明了方向。

2. 使用 .NET Alloc 拦截

在 PerfView 中有一个 .NET Alloc 选项,它可以拦截每一次对象分配,然后记录下 线程调用栈,再根据分配量计算权重,知道原理后,接下来就可以开启 .NET Alloc 拦截。

需要注意的是,对于这个选项,需要先开启收集,再启动程序,等程序执行完毕后,点击 Stop Collection ,稍等片刻,会看到如下截图。

点击 GC Heap Net MEM (Coarse Sampling) Stack 列表,选择我们的进程,会看到当前的 System.String 权重占比最高,所以调查它的分配源就是当务之急了,截图如下:

接下来双击 System.String 行,查看它的 Callers,逐一往下翻,终于找到了 Program.Alloc1() 方法,截图如下:

到这里就找到了问题函数 Alloc1() ,接下来就是探究源码了哈。

3. 生产中可以用 .NET Alloc 吗

现在大家都知道 .NET Alloc 可以实现对象分配拦截,但是在生产场景中,每秒的分配量可能达到几十万,上百万,每一次分配都要拦截,会产生诸多的负面影响。

1) 程序速度变慢。

2) 产生非常大的 zip 文件。

如果你不在意的话,可以这么使用,如果在意,建议用 .NET SampAlloc 选项,它是一种采样的方式,每秒中的同类型分配最多只会采样 100 次,所以在 性能zip文件 两个维度可以达到最优状态。

接下来勾选 .NET SampAlloc 项,其他操作步骤一致,截图如下:

有点意思的是,观察到的占比都是 43.7% ,哈!

PerfView专题 (第五篇):如何寻找 C# 托管内存泄漏的更多相关文章

  1. PerfView专题 (第四篇):如何寻找 C# 中程序集泄漏

    一:背景 前两篇我们都聊到了非托管内存泄漏,一个是 HeapAlloc ,一个是 VirtualAlloc,除了这两种泄漏之外还存在其他渠道的内存泄漏,比如程序集泄漏,这一篇我们就来聊一聊. 二: 程 ...

  2. PerfView专题 (第三篇):如何寻找 C# 中的 VirtualAlloc 内存泄漏

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

  3. PerfView专题 (第八篇):洞察 C# 内存泄漏之寻找静态变量名和GC模式

    一:背景 这篇我们来聊一下 PerfView 在协助 WinDbg 分析 Dump 过程中的两个超实用技巧,可能会帮助我们快速定位最后的问题,主要有如下两块: 洞察内存泄漏中的静态大集合变量名. 验证 ...

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

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

  5. PerfView专题 (第十一篇):使用 Diff 功能洞察 C# 内存泄漏增量

    一:背景 去年 GC架构师 Maoni 在 (2021 .NET 开发者大会) [https://ke.segmentfault.com/course/1650000041122988/section ...

  6. PerfView专题 (第六篇):如何洞察 C# 中 GC 的变化

    一:背景 在洞察 GC 方面,我觉得市面上没有任何一款工具可以和 PerfView 相提并论,这也是为什么我会在 WinDbg 之外还要学习这么一款工具的原因,这篇我们先简单聊聊 PerfView 到 ...

  7. PerfView专题 (第七篇):如何洞察触发 GC 的 C# 代码?

    一:背景 上一篇我们聊到了如何用 PerfView 洞察 GC 的变化,但总感觉还缺了点什么? 对,就是要跟踪到底是什么代码触发了 GC,这对我们分析由于 GC 导致的 CPU 爆高有非常大的参考价值 ...

  8. Java提高篇—— 简单介绍Java 的内存泄漏

    java最明显的一个优势就是它的内存管理机制.你只需简单创建对象,java的垃圾回收机制负责分配和释放内存.然而情况并不像想像的那么简单,因为在Java应用中经常发生内存泄漏. 本教程演示了什么是内存 ...

  9. Android 性能篇 -- 带你领略Android内存泄漏的前世今生

    基础了解 什么是内存泄漏? 内存泄漏是当程序不再使用到的内存时,释放内存失败而产生了无用的内存消耗.内存泄漏并不是指物理上的内存消失,这里的内存泄漏是指由程序分配的内存但是由于程序逻辑错误而导致程序失 ...

随机推荐

  1. Matplotlib的小入门

    Matplotlib专门用于开发2D图表(包括3D图表),在日常数据处理中经常需要运用到它,它的用法非常多样,这里记录一些基础用法,算是一个小入门,后面如果有更复杂的画图要求,再进一步学习. 如果有需 ...

  2. 绿色城市智慧运营:Web 3D 垃圾分类 GIS 系统

    前言 感谢所有为上海疫情奉献的人,祈求上海疫情早日清零,中国加油,上海加油! <上海市生活垃圾管理条例>施行至今已有两年多,上海市民践行绿色低碳理念.主动参与生活垃圾分类的习惯基本养成,分 ...

  3. 怎样生成分布式的流水ID

    流水编号 日常在我们开发的过程中可能会用到编号的功能,如销售订单号,采购订单号,日志编号,凭证号...等等,为了保证唯一有些表的主键要么用自增长,要么用GUID值,或通过雪花ID算法生成.这此方式基本 ...

  4. 2 万字 + 20张图| 细说 Redis 九种数据类型和应用场景

    作者:小林coding 计算机八股文网(操作系统.计算机网络.计算机组成.MySQL.Redis):https://xiaolincoding.com 大家好,我是小林. 我们都知道 Redis 提供 ...

  5. 谷歌浏览器Chrome官方下载地址

    经常看到朋友的电脑上安装是魔改的谷歌浏览器.这里将谷歌浏览器官方的下载地址放在这里.有需要的朋友可以自己去下载. 下载地址 Chrome最新稳定在线安装版:https://www.google.cn/ ...

  6. CSS 网页字体最佳实践

    一般在网页的字体设置中,可以将字体分类三类: 系统字体:使用系统自带的字体 兜底字体:当系统字体无法正常使用,而兜底的字体 Emoji 字体:显示网页中的表情字体 为了满足不同平台,以及 Emoji ...

  7. 《阿里云天池大赛赛题解析》——O2O优惠卷预测

    赛事链接:https://tianchi.aliyun.com/competition/entrance/231593/introduction?spm=5176.12281925.0.0.7e157 ...

  8. Django快速入门之项目配置

    开始 环境 python:3.6.2 django:2.0.5 跑起来 用pycharm导入或新建一个Django项目,在目录中存在manage.py的文件,通过下列指令运行Django后台. pyt ...

  9. SuperSocket 1.6 创建一个简易的报文长度在头部的Socket服务器

    我们来做一个头为6位报文总长度,并且长度不包含长度域自身的例子.比如这样的Socket报文000006123456. 添加SuperSocket.Engine,直接使用Nuget搜索SuperSock ...

  10. DBPack 读写分离功能发布公告

    在 v0.1.0 版本我们发布了分布式事务功能,并提供了读写分离功能预览.在 v0.2.0 这个版本,我们加入了通过 UseDB hint 自定义查询请求路由的功能,并修复了一些 bug.另外,在这个 ...