.NET 内存泄漏的争议
前几天发布了几篇关于要小心使用 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 内存泄漏的争议的更多相关文章
- 使用Memory Analyzer tool(MAT)分析内存泄漏(一)
转载自:http://www.blogjava.net/rosen/archive/2010/05/21/321575.html 前言 在平时工作过程中,有时会遇到OutOfMemoryError,我 ...
- (转)从内存管 理、内存泄漏、内存回收探讨C++内存管理
http://www.cr173.com/html/18898_all.html 内存管理是C++最令人切齿痛恨的问题,也是C++最有争议的问题,C++高手从中获得了更好的性能,更大的自由,C++菜鸟 ...
- 调不尽的内存泄漏,用不完的Valgrind
调不尽的内存泄漏,用不完的Valgrind Valgrind 安装 1. 到www.valgrind.org下载最新版valgrind-X.X.X.tar.bz2 2. 解压安装包:tar –jxvf ...
- ThreadLocal以及内存泄漏
ThreadLocal是什么 ThreadLocal 的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度.但是如果滥用Thr ...
- 【知识必备】内存泄漏全解析,从此拒绝ANR,让OOM远离你的身边,跟内存泄漏say byebye
一.写在前面 对于C++来说,内存泄漏就是new出来的对象没有delete,俗称野指针:而对于java来说,就是new出来的Object放在Heap上无法被GC回收:而这里就把我之前的一篇内存泄漏的总 ...
- Android性能优化之利用Rxlifecycle解决RxJava内存泄漏
前言: 其实RxJava引起的内存泄漏是我无意中发现了,本来是想了解Retrofit与RxJava相结合中是如何通过适配器模式解决的,结果却发现了RxJava是会引起内存泄漏的,所有想着查找一下资料学 ...
- Android性能优化之利用LeakCanary检测内存泄漏及解决办法
前言: 最近公司C轮融资成功了,移动团队准备扩大一下,需要招聘Android开发工程师,陆陆续续面试了几位Android应聘者,面试过程中聊到性能优化中如何避免内存泄漏问题时,很少有人全面的回答上来. ...
- C++的内存泄漏检测
C++大量的手动分配.回收内存是存在风险的,也许一个函数中一小块内存泄漏被重复放大之后,最后掏空内存. 这里介绍一种在debug模式下测试内存泄漏的方法. 首先在文件的开头以确定的顺序写下这段代码: ...
- 使用 Android Studio 检测内存泄漏与解决内存泄漏问题
本文在腾讯技术推文上 修改 发布. http://wetest.qq.com/lab/view/63.html?from=ads_test2_qqtips&sessionUserType=BF ...
随机推荐
- Python arange
原文来自DeniuHe.原文链接 >>> np.arange(3) array([0, 1, 2]) >>> np.arange(1,3,0.3) array([ ...
- “3+3”看华为云FusionInsight如何引领“数据新基建”持续发展
摘要:一个统一的现代化的数据基建需要三类架构来实践三种不同的应用场景. 近期,美国知名科技企业风投机构A16Z总结出一套通用的技术架构服务,分为以下三种场景. 一.数据基建架构全景 数据流向显示,左侧 ...
- grep/字符/次数匹配/锚定符/小大括号/wc/tr/cut/sort/uniq
grep:正则表达式,文本过滤工具,能够实现以指定的"模式(Pattern)"逐行搜索文件中的内容,并将匹配到的行显示出来. 模式:是由正则表达式的元字符,其他字符组合起来的匹配字 ...
- Flink处理函数实战之一:深入了解ProcessFunction的状态(Flink-1.10)
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- go get以后下载的包不在src下而在pkg的问题
我的GOPATH是这样的 但是当我go get下载包之后 下载的却不在src,而是在 $GOPATH$/pkg 下 原因可能是之前第一次go get下载, GitHub的速度太慢了,我更改了代理,使用 ...
- centos8 连接wifi
从官网下载的6G多的iso安装后 #ifconfig -a 如下 enp3s0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500l ...
- SpringSecurity之授权
SpringSecurity之授权 目录 SpringSecurity之授权 1. 写在前面的话 2. web授权 1. 建库 2. 添加查询权限的接口 3. 前端页面的编写 4. SpringSec ...
- 将多个PDF文件整合成一个文件
pdfFactory不仅可以将单个文件创建为PDF文件进行打印,还可以将多个文件整合为一个PDF文件,同时,也可以随时删除其中的一些文件,创建新的PDF文件. 图1:pdfFactory工具界面 一. ...
- 关于Boom 3D预设音效的妙用,如何鉴赏音乐流派
音乐流派,亦可理解为音乐的曲风.类型.不同的音乐流派表达的音乐形式也更不相同.例如民族音乐.古典音乐等这种传统乐器的应用,流行音乐则更注重节奏.韵律的变化.带给我们的音乐感受也不尽相同. Boom 3 ...
- springboot打jar包将引用的第三方包、配置文件(.properties、.xml)、静态资源打在包外
1.外置配置文件 Springboot读取核心配置文件(.properties)的外部配置文件调用方式为 jar包当前目录下的/config目录 因此要外置配置文件就在jar所在目录新建config文 ...