2010年2月16日 星期二 20时39分
如下内容,是自己乱想的,不代表正确性的。。
.net中资源(相对于内存来说)分两种,托管资源和非托管资源。
非托管资源,有形如如下的几种:数据库、文件等
在.net中,托管资源由CLR控制,由GC(Garbage Collector)负责释放。非托管资源,一般需要实现IDisposable来负责实现资源的回收释放。
堆内存分配
到第一个有足够空间的内存地址(没被占用的),然后将该内存分配。
托管堆中内存的分配是顺序的,也就是说一个挨着一个的分配。这样内存分配的速度就要比原始程序高,但是高出的速度会被GC找回去
GC工作方式
托管代码中的对象什么时候回收我们管不了(除非用GC.Collect强迫GC回收)。GC会在它"高兴"的时候执行一次回收(这有许多原因,比如内存不够用时。这样做是为了提高内存分配、回收的效率)。那么如果用Destructor呢?同样不行,因为.NET中Destructor的概念已经不存在了,它变成了Finalizer,为什么呢?在.NET中由于GC的特殊工作方式,Destructor并不实际存在,事实上,当调用Destructor的语法时,编译器会自动将它写为protected virtual void Finalize();
GC步骤
1:确定对象没有任何引用
2:检查对象是否在Finalizer表上有记录(如果类定义的时候,提供了析构函数,则在实例化对象的时候,则会在Finalizer表中增加一条记录)
3:如果在Finalizer表上有记录,那么将记录移到另外的一张表上(Finalizer2)。
4:如果不在Finalizer2表上有记录,那么释放内存
对于第4步,Finalizer2中的记录,又是什么时候删除呢?
在Finalizer2表上的对象的Finalizer会在另外一个low priority的线程上执行后从表上删除
从如上的步骤,可以看出,有析构函数的对象,不会马上被回收(代码1),只有执行一次以上的GC回收,才会被回收。
所以说,除非是绝对的需要,请不要提供析构函数。
代码1:
public class CountObject {
public static int Count = 0;
public CountObject() {
Count++;
}
~CountObject() {
Count--;
}
}
static void Main() {
CountObject obj;
for (int i = 0; i < 5; i++) {
// GC.Collect();(就算执行 GC.Collect,也不会被删除)
obj = null; // 这一步多余,这么写只是为了更清晰些!
obj = new CountObject();
}
// Count不会是1,因为Finalizer不会马上被触发,要等到有一次回收操作后才会被触发。
Console.WriteLine(CountObject.Count);
Console.ReadLine();
}
GC为了提高回收的效率使用了Generation的概念,原理是这样的,第一次回收之前创建的对象属于Generation 0,之后,每次回收时这个Generation的号码就会向后挪一,也就是说,第二次回收时原来的Generation 0变成了Generation 1,而在第一次回收后和第二次回收前创建的对象将属于Generation 0。GC会先试着在属于Generation 0的对象中回收,因为这些是最新的,所以最有可能会被回收,比如一些函数中的局部变量在退出函数时就没有引用了(可被回收)。如果在Generation 0中回收了足够的内存,那么GC就不会再接着回收了,如果回收的还不够,那么GC就试着在Generation 1中回收,如果还不够就在Generation 2中回收,以此类推。Generation也有个最大限制,根据Framework版本而定,可以用GC.MaxGeneration获得。在回收了内存之后GC会重新排整内存,让数据间没有空格,这样是因为CLR顺序分配内存,所以内存之间不能有空着的内存。现在我们知道每次回收时都会浪费一定的CPU时间,所以,不推荐手动GC.Collect。
非托管资源的释放
IDisposable接口,按照.NET Framework的标准,所有有需要手动释放非托管资源的类都得实现此接口。
如下的代码,是一个经典的代码写法:
public class Base : IDisposable {
public void Dispose() {
this.Dispose(true);
GC.SupressFinalize(this);(对象从Finalizer表去掉)
}
protected virtual void Dispose(bool disposing) {
if (disposing) {
// 托管类
}
// 非托管资源释放
}
~Base() {
this.Dispose(false);//为什么提供析构函数,是因为避免忘记手动调用Dispose方法。为什么不使用GC.SupressFinalize(this)呢?因为调用析构函数的时候,就已经把对象从Finalizer表去掉了,因为只有对象没有被任何引用的时候,才会调用析构函数的嘛。。
}
}
总结
一个对象只当在没有任何引用的情况下才会被回收。
一个对象的内存不是马上释放的,GC会在任何时候将其回收。
一般情况下不要强制回收工作。
如果没有特殊的需要不要写Finalizer。
不要在Finalizer中写一些有时间逻辑的代码。
在任何有非托管资源或含有Dispose的成员的类中实现IDisposable接口。
当用胖对象时可以考虑弱引用的使用。
有事Q我:
姓名:颜昌钢
联系方式:yanchanggang@boco.com.cn
电话:13886086508
QQ:95550107
公司:亿阳集团武汉分公司
移动飞信:647360243