分享.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. hdu 4932 Miaomiao&#39;s Geometry(暴力)

    题目链接:hdu 4932 Miaomiao's Geometry 题目大意:在x坐标上又若干个点,如今要用若干条相等长度的线段覆盖这些点,若一个点被一条线段覆盖,则必须在这条线的左端点或者是右端点, ...

  2. 勾选Create git respository的作用

    在Xcode中创建项目时会弹出Source Control选项,勾选Create git repository选项可以帮助我们对照以前项目中代码中修改的部分,为开发提供方便. 在项目完成到一定程度时, ...

  3. style中position的属性值具体解释

    Position的英文原意是指位置.职位.状态.也有安置的意思.在CSS布局中,Position发挥着非常关键的数据,非常多容器的定位是用Position来完毕. Position属性有四个可选值,它 ...

  4. Hack 语言学习/参考---1.3 Summary

    Summary Hack provides the following, non-exhaustive list of features: Ability to annotate function a ...

  5. 转载:Linux Used内存到底到哪里去了?

    转自:http://blogread.cn/it/article/6264?f=wb2 有时在Linux下会碰到这样的问题:ps aux看到的RSS内存只有不到30M,但是free看到内存却已经使用了 ...

  6. Linux下访问文件的基本模式

    源址:http://blogread.cn/it/article/6523?f=wb 访问文件的操作主要是指读文件和写文件,下文简单说明内核中几种常见的访问文件的方式. 普通模式 读写系统调用的默认方 ...

  7. [译]关于Java 字符串最常被问到的十个问题

    (说明,该文章翻译自Top 10 questions of Java Strings) 下面是关于Java字符串最常被问到的十个问题 1.怎么去比较字符串?使用==还是使用equals()? 简单来说 ...

  8. Tabs - 选项卡插件

        接上篇Tabs  - 选项卡插件  其中12)Yet (E)Another Tab Interface没有依赖任何javascript框架,以作补充          9)Flipping C ...

  9. 读书笔记—CLR via C#章节11-13

    前言 这本书这几年零零散散读过两三遍了,作为经典书籍,应该重复读反复读,既然我现在开始写博了,我也准备把以前觉得经典的好书重读细读一遍,并且将笔记整理到博客中,好记性不如烂笔头,同时也在写的过程中也可 ...

  10. 控制台命令行API

    js调试系列: 控制台命令行API   上次初步介绍了什么是控制台<js调试系列: 初识控制台>,以及简单的 console.log 输出信息.最后还有两个小问题,我们就当回顾,来看下怎么 ...