分享.net常见的内存泄露及解决方法

关于内存泄漏的问题,之前也为大家介绍过,比如:《C++中内存泄漏的检测方法介绍》,是关于C++内存泄漏的。今天为大家介绍的是关于.NET内存泄漏的问题。

  前段时间帮项目组内做了一次内存优化,产品是使用c#开发的winForm程序,一直以为.net提供了垃圾收集机制,开发的时候也没怎么注意内存的释放,导致最后的产品做出来之后,运行一个多小时就内存直接崩溃了,看来.net的垃圾收集还是得需要开发者加以控制,也不是万能的啊。

  下面将对垃圾收集做以简介,然后描述一下我在内存优化过程中常见的内存泄露及解决方法。

  托管堆的内存分配(下文中的托管堆指的是GC堆)

  托管堆是以应用程序域为依托的,即每一个应用程序域有一个托管堆,每一个托管堆也只属于一个应用程序域,且托管堆是一块连续的内存,其中的对象也是紧密排列的。相对于C++中的非连续内存堆来说,托管堆的内存分配效率要高。托管堆维护了一个指针,指向当前已使用内存的末尾,当需要分配内存的时候,只需要指针向后移动指定数量的位置即可。而且托管堆通过应用程序域实现了应用程序之间内存的隔离,即不同的应用程序域之间在正常情况下是不能相互访问各自的托管堆的。

  垃圾收集

  垃圾收集的算法有很多。例如引用计数、标记清除等等,托管堆使用的标记清除算法。

  托管堆使用的是分代标记清除算法。

  标记清除算法

  首先,系统将托管堆内所有的对象视为可以回收的垃圾,然后系统从GCRoot开始遍历托管堆内所有的对象,将遍历到的对象标记为可达对象,在遍历完成之后,回收所有的非可达对象,完成一遍垃圾收集。

  注意,托管堆的垃圾收集只会自己收集托管对象!

  由于在执行完垃圾收集之后,托管堆中会产生很多的内存碎片,导致内存不再连续,因此在垃圾收集完成之后,系统会执行一次内存压缩,将不连续的内存重新排列整齐,变成连续的内存。(关于垃圾收集的详细信息,大家可以参考《CLR Via C#》)

  通过上面的简述,大家都知道什么样的对象不会被收集,即能从GCRoot开始遍历到的对象。

  最常见的GCRoot是线程的栈,线程的栈里面通常包含方法的参数、临时变量等。另外常见的GCRoot还有静态字段、CPU寄存器以及LOH堆上的大的集合。因此,如果想要让托管对象的内存顺利的释放,只需要断开与跟之间的联系即可。而对于非托管对象的内存,必须进行手动释放。

  下面我根据自己在优化内存的过场中的一些常见错误以及一些解决方法。

  事件

  在.net内存泄露的原因当中,事件占据了非常大的一部分比例,事件是一种委托的实例,也就是与我们类中其他的字段一样,也是一个字段。

  委托为什么能阻止垃圾收集呢?即委托是如何让相关的对象在垃圾收集的时候被标记为可达对象的呢?首先要从委托的本质看起,

  我们通常使用的委托是从类

public abstract class MulticastDelegate : Delegate

  继承的,MulticastDelegate内部维护了一个private object _invocationList;,即我们通常所有的委托链(ps:委托链同字符串一样,是不可变的),这个委托链是以个object [],内部保存了Delegate对象,及每一个委托实际上是一个Delegate对象,而Delegate包含了两个非常重要的字段:

  1. internal object _target;
  2. internal object _methodBase;

  其中_target就是订阅事件的对象,_methodBase则是订阅事件的方法的 MethodInfo。其关联关系如下例所示:

  1. Code:
  2. public event EventHandler TestEvent;
  3. void MethodEndTempVarClear()
  4. {
  5. Test tempTestEvent = new Test();
  6. TestEvent += tempTestEvent.TestEvent;
  7. }

我们假设此段代码所在的对象即为一个可达的对象,则其引用关系如下图所示:

  由上图我们可以看出,原本应该在方法结束后就可以变为不可达对象的tempTestEvent变成了可达对象,因此也不能对其进行收集了。

  个人建议:将类中所有的事件订阅添加到一个专门的方法当中,且实现一个与其匹配的取消订阅的方法,并在必要的时候,调用取消订阅的方法。

  非托管对象

  非托管对象无论在什么时候,都不会被垃圾收集所回收,必须手动释放。

  .net中的非托管资源都实现了IDispose接口,我们可以在使用的时候,使用using(){}类实现非托管资源的释放。

  其中有一种情况非常容易遗漏,即通过一个方法创建了一个非托管的对象,如下所示:

  1. public MemoryStream CreateAStream()
  2. {
  3. return new MemoryStream();
  4. }

  大家在使用的时候非常容易遗忘通过这种方法的形式创建的非托管对象,尤其是一些名字意义表达不准确的时候,例如

var temp = CreateATemp();//CreateATemp返回一个非托管对象

  大家可能会漏掉对temp的内存释放,因此建议大家尽量少用方法创建或者初始化非托管对象,如果需要,则使用如下的方式:

  1. bool InitializeStream(out MemoryStream stream)
  2. {
  3. stream = new MemoryStream();
  4. return true;
  5. }

  即使用out关键字,这样大家在使用这个方法的时候,需要首先声明相关的非托管对象,可以在使用完成之后,及时的释放,减少遗漏。

  集合/静态字段

  对于集合,我们在使用完成之后,需要即时的clear,尤其是将一些方法中的临时变量添加到集合当中之后,会导致集合膨胀,并使得其中的内存泄露。

  对于静态字段,我们应该尽量减少其可见的域,因为静态字段在整个程序运行期间都不会被释放,减少其可见域就减少了其内存泄露的可能性,注意,不到万不得以,千万不要声明静态的集合,就是使用了,那也一定要小心再小心。静态集合很容易造成内存泄露。

  最好,大家有什么好的建议后者方法,欢迎补充!!

分享.net常见的内存泄露及解决方法的更多相关文章

  1. JS高程中的垃圾回收机制与常见内存泄露的解决方法

    起因是因为想了解闭包的内存泄露机制,然后想起<js高级程序设计>中有关于垃圾回收机制的解析,之前没有很懂,过一年回头再看就懂了,写篇博客与大家分享一下. #内存的生命周期: 分配你所需要的 ...

  2. Android APP常见的5类内存泄露及解决方法

    1.static变量引起的内存泄漏 因为static变量的生命周期是在类加载时开始 类卸载时结束,也就是说static变量是在程序进程死亡时才释放,如果在static变量中 引用了Activity 那 ...

  3. 调用MediaScannerConnection 发生内存泄露的解决方法

    调用MediaScannerConnection发起扫描时经常会发生内存泄露,例如: E ActivityThread: Activity FolderListActivity has leaked ...

  4. java中常见的内存泄露的例子

    JAVA 中的内存泄露     Java中的内存泄露,广义并通俗的说,就是:不再会被使用的对象的内存不能被回收,就是内存泄露.     Java中的内存泄露与C++中的表现有所不同.     在C++ ...

  5. JavaScript内存泄露,闭包内存泄露如何解决

    本文原链接:https://cloud.tencent.com/developer/article/1340979 JavaScript 内存泄露的4种方式及如何避免 简介 什么是内存泄露? Java ...

  6. 谈谈.NET中常见的内存泄露问题——GC、委托事件和弱引用

    其实吧,内存泄露一直是个令人头疼的问题,在带有GC的语言中这个情况得到了很大的好转,但是仍然可能会有问题.一.什么是内存泄露(memory leak)?内存泄露不是指内存坏了,也不是指内存没插稳漏出来 ...

  7. JavaScript 中 4 种常见的内存泄露陷阱

    了解 JavaScript 的内存泄露和解决方式! 在这篇文章中我们将要探索客户端 JavaScript 代码中常见的一些内存泄漏的情况,并且学习如何使用 Chrome 的开发工具来发现他们.读一读吧 ...

  8. .NET中常见的内存泄露问题——GC、委托事件和弱引用

    一.什么是内存泄露(memory leak)? 内存泄露不是指内存坏了,也不是指内存没插稳漏出来了,简单来说,内存泄露就是在你期待的时间内你程序所占用的内存没有按照你想象中的那样被释放. 因此什么是你 ...

  9. JavaScript 中常见的内存泄露陷阱(摘)

    内存泄露是每个开发者最终都不得不面对的问题.即便使用自动内存管理的语言,你还是会碰到一些内存泄漏的情况.内存泄露会导致一系列问题,比如:运行缓慢,崩溃,高延迟,甚至一些与其他应用相关的问题. 什么是内 ...

随机推荐

  1. IOS开发中怎样验证邮箱的合法性

    IOS开发中怎样验证邮箱的合法性 文章参考:http://www.codes51.com/article/detail_94157.html 代码: - (void)viewDidLoad { [su ...

  2. Milo的游戏开发的一些链接资料

    http://www.cnblogs.com/miloyip/default.aspx?page=1 http://www.cnblogs.com/miloyip/archive/2010/06/14 ...

  3. POJ 3654 &amp; ZOJ 2936 &amp; HDU 2723 Electronic Document Security(模拟)

    题目链接: PKU:http://poj.org/problem?id=3654 ZJU:http://acm.zju.edu.cn/onlinejudge/showProblem.do?proble ...

  4. SQL点滴19—T-SQL中的透视和逆透视

    原文:SQL点滴19-T-SQL中的透视和逆透视 透视 今天抽一点时间来看看透视和逆透视语句,简单的说就是行列转换.假设一个销售表中存放着产品号,产品折扣,产品价格三个列,每一种产品号可能有多种折扣, ...

  5. Ubuntu Ruby On Rails安装和配置

    在这篇文章中ubuntu通过rvm安装ruby和rails.步借鉴了官方网站和网上信息,这里给大家分享. 1. 安装mapapis公钥: gpg --keyserver hkp://keys.gnup ...

  6. android微信付费

    原文地址:http://blog.csdn.net/intbird 微信官方文档地址:t=resource/res_main_tmpl&verify=1&lang=zh_CN" ...

  7. Google Hacking的实现以及应用

    Google Hacking其实并算不上什么新东西,在早几年我在一些国外站点上就看见过相关的介绍,但是由于当时并没有重视这种技术,认为最多就只是用来找找未改名的mdb或者别人留下的webshell什么 ...

  8. javascript call和apply

    每个函数都包含两个非继承而来的方法:call和apply. 我们可以通过这两个方法来间接调用函数.可以这样: f.call(o); f.apply(o); //o对象间接调用了f函数 这与下面的功能相 ...

  9. innerText与innerHTML的区别

    innerText与innerHTML的区别:1.innerText将所有文本内容作为普通的文本2.innerHTML会识别文本内容中是否含有html标签,它能够把html标签的效果显示出来3.inn ...

  10. MIT Introduction to Computer Science and Programming (Lesson one )

    MIT Introduction to Computer Science and Programming (Lesson one ) 这篇文是记载 MIT 计算机科学及编程导论 第一集 的笔记 Les ...