分享.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. href 做导航 特效

    <div> <div> <%for (int i = 0; i < 200; i++) { %><%=i.ToString() %> <br ...

  2. Android自己定义控件系列五:自己定义绚丽水波纹效果

    尊重原创!转载请注明出处:http://blog.csdn.net/cyp331203/article/details/41114551 今天我们来利用Android自己定义控件实现一个比較有趣的效果 ...

  3. Installshield设置feature为必须选中状态,即必定安装状态

    原文:Installshield设置feature为必须选中状态,即必定安装状态 上一篇: 解决卸载时残留目标文件夹的问题Installation Designer --> Organizati ...

  4. 原生JavaScript生成GUID的实现

    GUID(全局统一标识符)是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的.通常平台会提供生成GUID的API.生成算法很有意思,用到了以太网卡地址.纳秒级时间.芯片ID码和许多可 ...

  5. Javascript操作阵列

    头操作unshift和shift var arr = [1, 2, 3]; arr.unshift(4); // arr = [4, 1, 2, 3]; 头加 arr.shift(); // arr ...

  6. 企业部署Windows 8 Store 风格应用

    原文:企业部署Windows 8 Store 风格应用 引言 之前我们都知道可以将应用程序发布到Windows 商店中供用户下载使用.如果我们是企业开发人员,则我们的应用可能属于以下两种类别之一: 1 ...

  7. UpdateModel方法

    WebForm 对 MVC 说:能否借你的UpdateModel方法来用用? 背景 ASP.NET MVC的Controller有个很不错的方法:UpdataModel (相对应的还有TryUpdat ...

  8. 实例学习SSIS(四)--使用日志记录和错误流重定向

    原文:实例学习SSIS(四)--使用日志记录和错误流重定向 导读: 实例学习SSIS(一)--制作一个简单的ETL包 实例学习SSIS(二)--使用迭代 实例学习SSIS(三)--使用包配置 实例学习 ...

  9. 更改MYSQL数据库不区分大小写表名

    今天郁闷死了,在LINUX下调一个程序老说找不到表,但是我明明是建了表的,在MYSQL的命令行下也可以查到,为什么程序就找不到表呢? 后来请教了一个老师才搞定,原来是LINUX下的MYSQL默认是要区 ...

  10. javascript模仿块级作用域(第一篇)

    作用域有词法作用域和块级作用域之分,javascript属于词法作用域,而在java.C++中却是块级作用域.在javascript中,只有函数能够创建作用域,作用域是以function作为边界的. ...