谈谈.NET中常见的内存泄露问题——GC、委托事件和弱引用
其实吧,内存泄露一直是个令人头疼的问题,在带有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、委托事件和弱引用的更多相关文章
- .NET中常见的内存泄露问题——GC、委托事件和弱引用
一.什么是内存泄露(memory leak)? 内存泄露不是指内存坏了,也不是指内存没插稳漏出来了,简单来说,内存泄露就是在你期待的时间内你程序所占用的内存没有按照你想象中的那样被释放. 因此什么是你 ...
- 对开发中常见的内存泄露,GDI泄露进行检测
对开发中常见的内存泄露,GDI泄露进行检测 一.GDI泄露检测方法: 在软件测试阶段,可以通过procexp.exe 工具,或是通过任务管理器中选择GDI对象来查看软件GDI的对象是使用情况. 注意点 ...
- JavaScript 中常见的内存泄露陷阱(摘)
内存泄露是每个开发者最终都不得不面对的问题.即便使用自动内存管理的语言,你还是会碰到一些内存泄漏的情况.内存泄露会导致一系列问题,比如:运行缓慢,崩溃,高延迟,甚至一些与其他应用相关的问题. 什么是内 ...
- Android开发中常见的内存泄露案例以及解决方法总结
1.单例模式引起的内存泄露 由于单例模式的静态特性,使得它的生命周期和我们的应用一样长,如果让单例无限制的持有Activity的强引用就会导致内存泄漏如错误代码示例: public class Use ...
- java中常见的内存泄露的例子
JAVA 中的内存泄露 Java中的内存泄露,广义并通俗的说,就是:不再会被使用的对象的内存不能被回收,就是内存泄露. Java中的内存泄露与C++中的表现有所不同. 在C++ ...
- 分享.net常见的内存泄露及解决方法
分享.net常见的内存泄露及解决方法 关于内存泄漏的问题,之前也为大家介绍过,比如:<C++中内存泄漏的检测方法介绍>,是关于C++内存泄漏的.今天为大家介绍的是关于.NET内存泄漏的问题 ...
- JavaScript 中 4 种常见的内存泄露陷阱
了解 JavaScript 的内存泄露和解决方式! 在这篇文章中我们将要探索客户端 JavaScript 代码中常见的一些内存泄漏的情况,并且学习如何使用 Chrome 的开发工具来发现他们.读一读吧 ...
- C程序中常见的内存操作错误
对C/C++程序员来说,管理和使用虚拟存储器可能是个困难的, 容易出错的任务.与存储器有关的错误属于那些令人惊恐的错误, 因为它们在时间和空间上, 经常是在距错误源一段距离之后才表现出来. 将错误的数 ...
- 【转】《深入理解计算机系统》C程序中常见的内存操作有关的典型编程错误
原文地址:http://blog.csdn.net/slvher/article/details/9150597 对C/C++程序员来说,内存管理是个不小的挑战,绝对值得慎之又慎,否则让由上万行代码构 ...
随机推荐
- BZOJ1770 : [Usaco2009 Nov]lights 燈
设$f[i]$表示$i$点按下开关后会影响到的点的集合,用二进制表示. 不妨设$n$为偶数,令$m=\frac{n}{2}$,对于前一半暴力$2^m$搜索所有方案,用map维护每种集合的最小代价. 对 ...
- hdu Turn the corner
这题是道三分的题,首先要分析满足条件的情况,这个就是平面几何的功夫了.要想车子能够转弯成功,最上面那个点到水平线的距离要小于等于y.这里h和s的公式就是利用平面几何的知识求出来的:s=l*cos(a) ...
- [转]Jquery通用开源框架之【ejq.js】
ejq是一款非常小巧的JS工具库,未压缩才50K,在jquery的基础上对jquery缺失部分作了很好的弥补作用. 优点: 1.具有内置的模板解析引擎语法和angularjs相近减少学习成本 2.能够 ...
- php根据IP地址跳转对应的城市,淘宝REST api调用地址直接使用
<?php // 定义一个函数getIP() function getIP(){ global $ip; if (getenv("HTTP_CLIENT_IP")) { $i ...
- mac 下 word 2011 使用笔记
1. 全屏预览,最大限度减少干扰 点击左下角焦点视图图标进入焦点视图. 调整为页面宽度或单页,点击顶部右侧百分比调整. 2.无格式粘贴 option + shift + command + v 3.无 ...
- UITableview cell 的多选
利用NSMutableDictionary key值 来改变cell的状态 -(void)createUI{ table = [[UITableView alloc]initWithFrame:CG ...
- GDC 2016 神秘海域4中使用Substance制作Texture
TEXTURING UNCHARTED 4: A MATTER OF SUBSTANCE 原文链接 http://www.dualshockers.com/2016/03/16/amazing-unc ...
- PHP--进行模块化设计
PHP--进行模块化设计 [来源] 达内 [编辑] 达内 [时间]2012-10-30 导航模块可以简单列为一个关于三级页面链接的HTML文件.通常你可以通过用另一种颜色来标明对当前区域的链 ...
- Technical analysis of client identification mechanisms
http://www.chromium.org/Home/chromium-security/client-identification-mechanisms Chromium > Chro ...
- 每个配置xml的含义作用
参考链接 web.xml xxx-servlet.xml(例如:dispatcher-servlet.xml)是Spring MVC里的,控制器,拦截url,转发view,对应的是controller ...