谈谈.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++程序员来说,内存管理是个不小的挑战,绝对值得慎之又慎,否则让由上万行代码构 ...
随机推荐
- LightOJ1057 Collecting Gold(状压DP)
这道题可以想到几点: 整个行程可以看作一次次的行走,每次行走都是用最短的路程从某一非空点到达另外一非空点: 两点间最少的步数是二者x和y坐标差的最大值: 返回原点这个过程,肯定是取完最后一个黄金后直接 ...
- 【BZOJ】2648: SJY摆棋子 & 2716: [Violet 3]天使玩偶(kdtree)
http://www.lydsy.com/JudgeOnline/problem.php?id=2716 http://www.lydsy.com/JudgeOnline/problem.php?id ...
- 【BZOJ】1087: [SCOI2005]互不侵犯King(状压dp)
http://www.lydsy.com:808/JudgeOnline/problem.php?id=1087 状压dp是第一次写啊,我也是才学TAT.状压dp一般都用一个值表示集合作为dp的一个状 ...
- HDU 4417 Super Mario(划分树+二分)
题目链接 #include <cstdio> #include <cstring> #include <algorithm> using namespace std ...
- ZXing二维码的生成和解析
Zxing是Google提供的关于条码(一维码.二维码)的解析工具,提供了二维码的生成与解析的方法, 现在我简单介绍一下使用Java利用Zxing生成与解析二维码 注意: 二维码的生成需要借助辅助类( ...
- Spring整合Quartz实现持久化、动态设定时间
一.spring整合 网上一搜有很多整合的方式,这里我采用了其中的一种(暂时还没有对其他的方法研究过). 对于spring的整合其中的任务,spring提供了几个类.接口(这些类都实现了Job接口): ...
- 你能不用计算机来计算S=a+(a+1)+(a+2) + ...... + b的解的数目吗?
S=a + (a + 1) + (a + 2) + ...... + b(其中a, b > 0) 现在我们要求,给定一个正整数S,求有多少种不同的<a,b>,使得上述的等式成立. 这 ...
- 初始html5,遇到的第一个问题
1.首先感到html5是html的延续,只是增加了新的标签属性,这是我的第一感觉 2.我写了几行画矩形的canvas入门代码,遇到了不显示问题 3.下面是我写完代码后的问题 刷新后还是这样 4.我的代 ...
- Hibernate配置文件学习心得
Hibernate配置文件在工程中十分重要,名称为Hibernate.cfg.xml;如下图: 在代码模式下图: 第一句由于没怎么改动过,所以至今不知道有什么作用: <property name ...
- Office 2010 KMS激活原理和案例分享 - Your Office Solution Here - Site Home - TechNet Blogs
[作者:葛伟华.张玉工程师 , Office/Project支持团队, 微软亚太区全球技术支持中心 ] 为了减低部署盗版(可能包含恶意软件.病毒和其他安全风险)的可能性,Office 2010面向企 ...