前几天发布了几篇关于要小心使用 Task.Run 的文章,看了博客园的所有评论。发现有不少人在纠结示例中的现象是不是属于内存泄漏,本文分享一下我个人的看法,大家可以保留自己的意见。

在阅读本文前,如果你对 GC 分代算法还不了解,建议先阅读我的上一篇文章:小心使用 Task.Run 终篇解惑

背景

还是先把前面两篇文章的示例贴出来:

class Program
{
static void Main(string[] args)
{
Test(); GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect(); // 程序保活
while (true)
{
Thread.Sleep(100);
}
} static void Test()
{
var myClass = new MyClass();
myClass.Foo();
// 到这,myClass 实例不再需要了
}
} public class MyClass
{
private int _id; public Task Foo()
{
return Task.Run(() =>
{
Console.WriteLine($"Task.Run is executing with ID {_id}");
Thread.Sleep(100); // 模拟耗时操作
});
} ~MyClass()
{
Console.WriteLine("MyClass instance has been colleted.");
}
}

或许是我表述的问题,更或许是我把原本是一篇的文章折成了两篇发布,造成了一些误解。所以在这里我对后两篇的内容再解释一下。

有的童鞋可能误解了这个示例要演示的是什么。我演示的是,myClass 实例对象不再需要使用时,GC 在其成员被捕获的情况下能否把它回收掉。我特意用 Test() 方法包装了一下 MyClass 实例的创建和调用,当 Test() 方法执行结束时,myClass 对象则变成了不再需要使用的对象。为了保证 GC 强制回收时,myClass 对象的成员是被引用(捕捉)着的,我在 Task.Run 的匿名方法中使用了 Thread.Sleep(100)

如果在 while 循环内不断执行强制回收或者在强制回收前等待足够长的时间,保证 Task.Run 执行完,myClass 对象当然会被回收,因为此时它不存在被不可回收的资源捕获的成员,这点我本以为不需要示例演示大家应该也是这么认为的。如果你了解 GC 的分代算法,你关注的会是,当 myClass 对象变成不再需要使用的资源时,它能否被 GC 在 Gen 0 阶段被回收;而不是关注它最终会不会被回收。

在实际 GC 自动回收的情况下(非手动强制回收),如果第一次扫描到 myClass 发现它被其它对象引用,则会把它标记为 Gen 1,再扫描到它时就会把它标记为 Gen 2。每错过一次回收时机,在内存驻留的时间就越长,它就越难被回收。GC 进行 Root 搜索时,它是否会去搜索某个对象是有统计学基础的。

好了,现在切入正题。问:示例中的现象在 .NET 中是否属于内存泄漏?

正题

我们知道,.NET 应用程序主要使用三种类型的内存:堆栈托管堆非托管堆。绝大多数我们在 .NET 中使用的引用类型都是分配在托管堆上的,例如本文示例中的 myClass 对象。发生在托管堆上的内存泄漏我们可以把它称为托管内存泄漏

关于 .NET 托管堆上的内存泄漏,我直接引用其它两篇文章的现象描述吧(文章地址在文末)。

第一篇[1]描述的一个内存泄漏的现象是:

If the reference is stored in a field reference in the class where the method is declared, it’s not so smart, since it’s impossible to determine whether it will be reused later on, or at least very very hard. If this data structure becomes unnecessary, you should clear the reference you’re holding to it so that GC will pick it up later.

也说是在方法中捕获类成员的现象,和本文示例相符。如果对象不再需要使用了,你应该清除掉它“身上”的引用,以让 GC 在下一次搜索时把它回收掉。

第二篇[2](我的《为什么要小心使用Task.Run》文章就参考了这篇文章)是这样描述的:

There are 2 related core causes for memory leaks. The first core cause is when you have objects that are still referenced but are effectually unused. Since they are referenced, the GC won’t collect them and they will remain forever, taking up memory. This can happen, for example, when you register to events but never unregister. Let’s call this a managed memory leak.

和第一篇的意思差不多,也是说当对象实际上不再使用了,但因为它还被引用,GC 则不会回收它们,这种现象作者把它归为导致内存泄漏的一个主要原因。

第二篇[2]文中还有这么一段:

Many share the opinion that managed memory leaks are not memory leaks at all since they are still referenced and theoretically can be de-allocated. It’s a matter of definition and my point of view is that they are indeed memory leaks. They hold memory that can’t be allocated for another instance and will eventually cause an out-of-memory exception.

翻译如下:

很多人都认为,托管内存泄漏根本不是内存泄漏,因为它们仍然被引用,理论上可以去分配。这是一个定义的问题,我的观点是,它们确实是内存泄漏。它们持有的内存无法分配给另一个实例,最终可能会造成内存溢出异常。

简单概括就是很多人认为托管内存泄漏不属于内存泄漏,这具有争议性,作者认为这是定义问题。

维基上的定义是这样的:

内存泄漏(Memory leak)是在计算机科学中,由于疏忽或错误造成程序未能释放已经不再使用的内存。

这个定义并没有对内存泄漏在时间上设限,请注意“由于疏忽或错误”和“不再使用”这两个重要关键词。”未能释放“是永久还是长时间?并没有明确定义。如果你要说我是在咬文嚼字,嗯,随你吧。

一个 .NET 应用,托管堆中处于 Gen 2 的未回收资源会有很多,其中基本上都是需要使用的。

不需要再使用的资源长时间驻留在内存的托管堆上,它逃过了 Gen 0,逃过了 Gen 1,甚至逃过了 N 次 Gen 2,这是否属于内存泄漏,存在很大的争议。我认为这也是定义问题,站在操作系统的视角和托管堆“分代”的视角自然会得到不一样的理解。

就像最近头条上很多人对 1=0.999...(无限循环)这个数学问题的争议一样,有的人认为这个等式是对的,有的人认为它是错的。

最后,我选择以托管堆的视角来理解,我的观点和第二篇引用文的作者一样,因编码不当导致不再需要使用的资源长时间驻留内存(延迟回收),属于内存泄漏。延迟回收也属于代码缺陷,虽然,很多场景大可不必在意这点性能。大家随意,哪种更能帮助你理解你便选择哪种。

文中链接:

[1]. http://dwz.date/d48W

[2]. http://dwz.date/d48U

附前两篇文章链接:

小心使用 Task.Run 续篇

小心使用 Task.Run 终篇解惑

.NET 内存泄漏的争议的更多相关文章

  1. 使用Memory Analyzer tool(MAT)分析内存泄漏(一)

    转载自:http://www.blogjava.net/rosen/archive/2010/05/21/321575.html 前言 在平时工作过程中,有时会遇到OutOfMemoryError,我 ...

  2. (转)从内存管 理、内存泄漏、内存回收探讨C++内存管理

    http://www.cr173.com/html/18898_all.html 内存管理是C++最令人切齿痛恨的问题,也是C++最有争议的问题,C++高手从中获得了更好的性能,更大的自由,C++菜鸟 ...

  3. 调不尽的内存泄漏,用不完的Valgrind

    调不尽的内存泄漏,用不完的Valgrind Valgrind 安装 1. 到www.valgrind.org下载最新版valgrind-X.X.X.tar.bz2 2. 解压安装包:tar –jxvf ...

  4. ThreadLocal以及内存泄漏

    ThreadLocal是什么 ThreadLocal 的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度.但是如果滥用Thr ...

  5. 【知识必备】内存泄漏全解析,从此拒绝ANR,让OOM远离你的身边,跟内存泄漏say byebye

    一.写在前面 对于C++来说,内存泄漏就是new出来的对象没有delete,俗称野指针:而对于java来说,就是new出来的Object放在Heap上无法被GC回收:而这里就把我之前的一篇内存泄漏的总 ...

  6. Android性能优化之利用Rxlifecycle解决RxJava内存泄漏

    前言: 其实RxJava引起的内存泄漏是我无意中发现了,本来是想了解Retrofit与RxJava相结合中是如何通过适配器模式解决的,结果却发现了RxJava是会引起内存泄漏的,所有想着查找一下资料学 ...

  7. Android性能优化之利用LeakCanary检测内存泄漏及解决办法

    前言: 最近公司C轮融资成功了,移动团队准备扩大一下,需要招聘Android开发工程师,陆陆续续面试了几位Android应聘者,面试过程中聊到性能优化中如何避免内存泄漏问题时,很少有人全面的回答上来. ...

  8. C++的内存泄漏检测

    C++大量的手动分配.回收内存是存在风险的,也许一个函数中一小块内存泄漏被重复放大之后,最后掏空内存. 这里介绍一种在debug模式下测试内存泄漏的方法. 首先在文件的开头以确定的顺序写下这段代码: ...

  9. 使用 Android Studio 检测内存泄漏与解决内存泄漏问题

    本文在腾讯技术推文上 修改 发布. http://wetest.qq.com/lab/view/63.html?from=ads_test2_qqtips&sessionUserType=BF ...

随机推荐

  1. netfilter内核态与用户态 通信 之 sockopt

    用户态与内核态交互通信的方法不止一种,sockopt是比较方便的一个,写法也简单.缺点就是使用 copy_from_user()/copy_to_user()完成内核和用户的通信, 效率其实不高, 多 ...

  2. gethostname(获取主机名)、gethostbyname(由主机名获取IP地址)

    int gethostname(char *name, size_t len);获取本地主机名存入name[len],成功返回0,失败返回-1: struct hostent * gethostbyn ...

  3. thinkphp5.1与layui table表格使用

    第1部分:layui 的 html代码, 即第2部分 thinkphp 控制器方法 index/Dataz/returnShowUser 的view页面 <!DOCTYPE html> & ...

  4. ubuntu配置bonding

    如果节点上有多个网络接口时可以通过bonding将多个网络接口虚拟为一个网络接口,bonding可以提供高可用及负载均衡功能,从而提高节点的网络接口性能及可用性. 配置单bond 一.使用如下命令安装 ...

  5. [原题复现]2018护网杯(WEB)easy_tornado(模板注入)

    简介 原题复现:  考察知识点:模板注入  线上平台:https://buuoj.cn(北京联合大学公开的CTF平台) 榆林学院内可使用信安协会内部的CTF训练平台找到此题 [护网杯 2018]eas ...

  6. 探究:nuget工具对不再使用的dll文件的处理策略

    背景介绍 nuget是.net平台有效的包管理工具,相信每个C#开发者对它都不陌生. 本文我们来探究一下nuget对不再使用的dll文件的处理策略,分为如下2个场景: 场景A:包A1.0原来包含New ...

  7. C++深拷贝与浅拷贝区别

    浅拷贝只是对指针的拷贝,浅拷贝后两个指针指向同一个内存空间: 深拷贝不仅对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针. 当对一个已知对象进行拷贝时,编译系统会 ...

  8. vue项目中echarts属性总结

    <div id="echarts" style="width: 600px;height: 400px;margin-top: 100px;margin-left: ...

  9. 左右声道音频怎么制作,用Vegas就对啦

    一款优秀的视频剪辑软件,不仅有高水平的视频制作功能,它的音频编辑功能也是必不可少的.Vegas就是这么一款软件,同时具备视频制作特效制作的同时,还能帮助制作轨道音频效果. 下面,就让小编带大家去学习, ...

  10. 头秃了,使用@AutoConfigureBefore指定配置类顺序竟没生效?

    持续原创输出,点击上方蓝字关注我 前言 日常工作中对于Spring Boot 提供的一些启动器可能已经足够使用了,但是不可避免的需要自定义启动器,比如整合一个陌生的组件,也想要达到开箱即用的效果. 在 ...