.net常见的内存泄露
.net常见的内存泄露内存泄露一直是个令人头疼的问题,在带有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中最常见的内存泄露问题的原因之一。
二、常用的一些内存泄漏的原因
- 使用静态引用
- 未退订的事件-作者认为这是最常见的内存泄漏原因
- 未退订的静态事件
- 未调用Dispose方法
- 使用不彻底的Dispose方法
- 在Windows Forms中对BindingSource的误用
- 未在WorkItem/CAB上调用Remove
三、常用的一些避免内存泄漏的建议
1、Dispose()的使用
如果使用的对象提供Dispose()方法,那么当你使用完毕或在必要的地方(比如Exception)调用该方法,特别是对非托管对象,一定要加以调 用,以达到防止泄露的目的。另外很多时候程序提供对Dispose()的扩展,比如Form,在这个扩展的Dispose方法中你可以把大对象的引用什么 的在退出前释放。
对于DB连接,COM组件(比如OLE组件)等必须调用其提供的Dispose方法,没有的话最好自己写一个。
2、using的使用
using除了引用Dll的功用外,还可以限制对象的适用范围,当超出这个界限后对象自动释放
3、事件的卸载
这个不是必须的,推荐这样做。之前注册了的事件,关闭画面时应该手动注销,有利于GC回收资源。
4、API的调用
一般的使用API了就意味着使用了非托管资源,需要根据情况手动释放所占资源,特别是在处理大对象时。
5、继承 IDisposable实现自己内存释放接口
Net 如何继承IDisposable接口,实现自己的Dispose()函数
6、弱引用(WeakReference )
通常情况下,一个实例如果被其他实例引用了,那么他就不会被GC回收,而弱引用的意思是,如果一个实例没有被其他实例引用(真实引用),而仅仅是被弱引 用,那么他就会被GC回收。
委托的本质就是一个类,包含了几个关键属性:
(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);
}
}
7、析构函数(Finalize())
使用了非托管资源的时候,可以自定义析构函数使得对象结束时释放所占资源;
对仅使用托管资源的对象,应尽可能使用它自身的Dispose方法,一般不推荐自定义析构函数。