其实吧,内存泄露一直是个令人头疼的问题,在带有GC的语言中这个情况得到了很大的好转,但是仍然可能会有问题。

一、什么是内存泄露(memory leak)?

内存泄露不是指内存坏了,也不是指内存没插稳漏出来了,简单来说,内存泄露就是在你期待的时间内你程序所占用的内存没有按照你想象中的那样被释放。

因此什么是你期待的时间呢?明白这点很重要。如果一个对象占用内存的时间和包含这个对象的程序一样长,但是你并不期望是这样。那么就可以认为是内存泄露了。用具体例子来说明如下:

class Button {public void OnClick(object sender, EventArgs e) {...}
}
class Program {static event EventHandler ButtonClick;static void Main(string[] args) {Button button = new Button();ButtonClick += button.OnClick;    }
}

上面这段代码中,我们使用了一个静态的事件,而静态成员的生命周期是从AppDomain被加载开始,直到AppDomain被卸载,也就是说在通常情况下如果进程没被关闭,又忘记取消注册事件,那么ButtonClick事件包含的EventHandler委托所引用的对象会一直存在到进程结束为止,这就造成了内存泄露问题。这也是.NET中最常见的内存泄露问题的原因之一。后面我会接着说怎么解决这种事件造成的泄露问题。

二、内存回收的方式

1、引用计数

引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另一个 变量,则该值的引用次数加1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减1。当这个值的引用次数变成0时,则说明没有办 法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾收集器下次再运行时,它就会释放那些引用次数为零的值所占用的内存。

像原来IE6中Javascript中原生对象内存回收的方式就是通过检查对象是否有引用来判断一个对象是否是垃圾。IE9之前,其BOM和DOM中的对象是使用C++以COM对象的形式实现的,而COM对象的垃圾收集机制采用的也是引用计数策略。而这种方式通常会因为循环引用导致内存泄露,也就是A引用B的同时,B也引用者A。 在Objective-C中也会有这样的循环引用的问题。在Objective-C中的解决方案就是给一方标记为weak,介绍可以参看这里,关于Objective-C中的委托模式的介绍。

2、标记清除法(mark-weep)

C#中采用的是标记法回收内存,全部对象都要标记,并且只标记一次就不再标记。判断一个对象是不是垃圾取决于是否有引用,而是取决是是否被root引用。

root的类型有寄存器中的变量,线程栈上的变量,静态变量等。

我们来看一幅通常情况下的对象图,图中有一个循环引用。

我们抽取其中一部分图说明

在采用标记清除策略的实现中,由于函数执行之后,local3出栈,离开了作用域,因此这种相互引用在标记清除法中不是个问题。

我们很容易看出,因为每一个对象都要mark,因此创建大量的小对象会给Mark阶段造成压力。值得注意的是,在GC的mark和weep阶段,会挂起所有线程,因此创建大量的线程也是会对GC造成问题。这个问题我以后会再讨论。

三、弱引用解决一些问题

如前面所说,忘记取消注册事件通常是.NET中最常见的内存泄露问题,我们怎么自动化的解决这个问题呢?也就是说当方法所属的对象已经被标记为垃圾的时候,我们就在事件中取消注册这个方法。这时就可以通过弱引用来实现。

委托的本质就是一个类,包含了几个关键属性:

1. 指向原对象的Target属性(强引用)。

2. 一个指向方法的ptr指针。

3. 内部维护着一个集合(delegate是以链表结构实现)。

因为.NET中的委托是强引用,我们要把它改成弱引用,我们可以抓住这个这些特征,创建一个自己的WeakDelegate类。

事件的本质就是一个访问器方法,和委托的关系类似于字段和属性,也就是控制外部对字段的访问。我们可以通过自定义add和remove方法来把外部的委托转换成我们自己定义的委托。

public class Button
{private class WeakDelegate{public WeakReference Target;public MethodInfo Method;}private List<WeakDelegate> clickSubscribers = new List<WeakDelegate>();public event EventHandler Click{add{clickSubscribers.Add(new WeakDelegate{Target = new WeakReference(value.Target),Method = value.Method});}remove{.....}}public void FireClick(){List<WeakDelegate> toRemove = new List<WeakDelegate>();foreach (WeakDelegate subscriber in clickSubscribers){//第一个Target表示方法所属的对象,第二个Target表示这个对象是否被标记为垃圾,如果为null则表示为已经被标记为垃圾。object target = subscriber.Target.Target;if (target == null){toRemove.Add(subscriber);}else{subscriber.Method.Invoke(target, new object[] { this, EventArgs.Empty });}}clickSubscribers.RemoveAll(toRemove);}
}

  弱引用还可以用来创建一个对象池,对象池就是通过管理少量的对象来减少内存和GC压力。我们可以通过强引用来表示对象池内最小的对象数量,通过弱引用来表示可以达到的最大的数量。

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

  1. Android中常见的内存泄露

    内存泄漏是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成内存空间的浪费称为内存泄漏.内存泄露有时不严重且不易察觉,这样开发者就不知道存在内存泄露,但有时也会很严重,会 ...

  2. Android开发中常见的内存泄露案例以及解决方法总结

    Android开发中常见的内存泄露案例以及解决方法总结 参考文章: (1)Android开发中常见的内存泄露案例以及解决方法总结 (2)https://www.cnblogs.com/shen-hua ...

  3. java知识点8——垃圾回收原理和算法、通用的分代垃圾回收机制、 JVM调优和Full GC、开发中容易造成内存泄露的操作

    垃圾回收原理和算法 内存管理 Java的内存管理很大程度指的就是对象的管理,其中包括对象空间的分配和释放. 对象空间的分配:使用new关键字创建对象即可 对象空间的释放:将对象赋值null即可 垃圾回 ...

  4. Android内存泄露和GC机制

    Android内存泄露和GC机制 本文先对Android内存垃圾回收机制进行介绍,之后对分析.定位内存泄露常用的测试方法进行总结,分享给大家. 一.Android内存垃圾回收(GC机制) 1.综述 A ...

  5. java内部类内存泄漏,Android中常见的内存泄漏和解决方案

    什么是内存泄漏? 简单点说,就是指一个对象不再使用,本应该被回收,但由于某些原因导致对象无法回收,仍然占用着内存,这就是内存泄漏. 为什么会产生内存泄漏,内存泄漏会导致什么问题? 相比C++需要手动去 ...

  6. C程序中常见的内存操作错误

    对C/C++程序员来说,管理和使用虚拟存储器可能是个困难的, 容易出错的任务.与存储器有关的错误属于那些令人惊恐的错误, 因为它们在时间和空间上, 经常是在距错误源一段距离之后才表现出来. 将错误的数 ...

  7. 错误内存【读书笔记】C程序中常见的内存操作有关的典型编程错误

    题记:写这篇博客要主是加深自己对错误内存的认识和总结实现算法时的一些验经和训教,如果有错误请指出,万分感谢. 对C/C++程序员来讲,内存管理是个不小的挑战,绝对值得慎之又慎,否则让由上万行代码构成的 ...

  8. 常见造成内存泄露的原因

    内存泄露造成原因 1.上下文对象 2.碎片 3.WindowManager等系统管理类 4.Map 5.List和Map類似 6.接口回调 7.View 8.handler 9.线程 10.第三方库 ...

  9. 魔鬼的梦魇—验证IE中的js内存泄露模式(三)

    魔鬼的梦魇-验证IE中的js内存泄露模式(三) 按照Justin Rogers文章的顺序,接下来的这个模式应该是跨页内存泄露模式(cross-page leak),但是由于这个模式产生的中间对象,我们 ...

最新文章

  1. 修改IDEA运行jsp文件的时候浏览器地址栏的虚拟访问路径网址
  2. Linux压缩和解压缩命令集
  3. Codeforces Round #653 (Div. 3)部分题解
  4. easyuefi只能在基于uefi启动的_苹果电脑怎么从u盘启动|苹果笔记本按哪个键选u盘启动...
  5. 例题3-4 猜数字游戏的提示(Master-Mind Hints, UVa 340)
  6. Python面向对象之继承
  7. c++ 11 移动语义
  8. 5G时代 我国在通信技术领域弯道超车指日可待
  9. js遮罩层以及移动端的上拉框
  10. 512M内存编译php出错
  11. Jzoj5237 最长公共子序列
  12. Linux IO 测试工具 fio命令
  13. C++ Primer Plus第二章课后编程答案
  14. vue中的组件 (全局注册和本地注册组件)
  15. 259年后,中国最大的皇家园林上云了
  16. 【03】AngularJS 简介
  17. cf701B Cells Not Under Attack
  18. 三线表里加小短线_三线表的规范格式
  19. 信息学奥赛NOIP/CSP-J初赛知识点汇总
  20. xlsxwriter去掉网格线_python之xlsxwriter模块(可操作xls/xlsx格式文件)

热门文章

  1. htc+one+m7+linux驱动,HTC One M7简易刷Recovery教程
  2. linux java maven_Linux安装java环境和maven
  3. 文件编码 linux,【原创】Linux基础之文件编码
  4. java 相同字符不连续_Java中字符串中连续相同字符去重方法
  5. 【引用】窗口处理技巧大全 vb(窗体控件)
  6. 疯狂挂载:Linux连接常用外部设备的方法
  7. 鸟叫就能黑掉AI系统,而且你根本察觉不到
  8. 消息称Face++明年上市,还曝光了财务数据
  9. 毕啸南专栏 | 对话百度王海峰:AI时代会产生新巨头
  10. CREELINKS平台_处理器CeAd资源使用说明(CeAd的配置与使用)