.Net内存回收机制
上一篇 /
下一篇 2008-06-28 16:09:41
/ 个人分类:C#
1. 在WINDOWS系统中,32位处理器上的每个进程最多可以使用4GB的内存。这4GB内存称为虚拟地址空间,或虚拟内存。
2. 值类型存放在堆栈中,引用类型存放在托管堆上。堆栈上的内存总是向下填充的,即由高地址空间向低地址空间填充;托管堆上的内存总是向上填充的,即由低地址空间向高地址空间分配。C#的托管堆与旧未托管堆(如从C++的堆)的区别在于压缩操作,即只要垃圾收集器释放了托管堆上的对象,就会压缩其它对象,把释放的内存块移动到堆的端部,再次形成一个连续的块,就会使为新对象分配内存空间时的速度非常快,这就是C#中创建新对象比C++中创建新对象快的原因。(C++的堆中就没有这种操作,内存释放后,已释放的内存会和未释放的内存混合在一起,导致连续的未被使用的内存块都非常小,这样的话当创建一个新的对象时,运行库必须搜索堆,才能找到足够大的内存块来存放新对象) (见excel文档)
3. 与C++的析构函数相比,C#析构函数的问题是它的不确定性。在删除C++对象时,其析构函数会立即执行。但由于垃圾收集器的工作方式,无法确定C#中的析构函数何时执行。所以不能在C#的析构函数中放置需要在某一时刻运行的代码,也不应使用能以任意顺序对不同类实例调用的析构函数。另一个问题是C#析构函数的执行会延迟对象最终从内存中删除的时间。没有析构函数的对象会在垃圾收集器的一次处理中从内存中删除,但有析构函数的对象需要两次处理才能删除:第一次调用析构函数时,没有删除对象,第二次调用才真正删除对象。另外,运行库使用一个线程来执行所有对象的Finalize()方法。如果频繁使用析构函数,而且使用它们执行长时间的清理任务,对性能的影响就会非常显著。
4. 当垃圾回收器的指针指向托管堆以外的内存空间时,就需要回收内存中的垃圾了。在这个过程中,垃圾回收器首先假设在托管堆中任何的对象都需要被回收。然后他在托管堆中寻找被根对象引用的对象(根对象就是全局,静态或处于活动中的局部变量连同寄存器指向的对象),找到后将他们加入一个有效对象的列表中,并在已搜索过的对象中寻找是否有对象被新加入的有效对象引用。直到垃圾回收器检查完任何的对象后,就有一份根对象和根对象直接或间接引用了的对象的列表,而其他没有在表中的对象就被从内存中回收。
当对象被加入到托管堆中时,假如他实现了finalize()方法,垃圾回收器会在他的终结
列表(finalization list)中加入一个指向该对象的指针。当该对象被回收时,垃圾回收器
会检查终结列表,看是否需要调用对象的finalize()方法。假如有的话,垃圾回收器将
指向该对象的指针加入一个完成器队列中,该完成器队列保存了那些准备调用finalize()
方法的对象。到了这一步对象还不是真正的垃圾对象。因此垃圾回收器还没有把他们从
托管堆中回收。
当对象准备被终结时,另一个垃圾回收器线程会调用在完成器队列中每个对象的finalize()方法。当调用完成后,线程将指针从完成器队列中移出,这样垃圾回收器就知道在下
一次回收对象时能够清除被终结的对象了。从上面能够看到垃圾回收机制带来的很大一
部分额外工作就是调用finalize()方法,因此在实际编程中研发人员应该避免在类中实
现finalize()方法。
对于finalize()方法的另一个问题是研发人员不知道什么时候他将被调用。他不像c++
中的析构函数在删除一个对象时被调用。为了解决这个问题,在.net中提供了一个接口
idisposable。微软建议在实现带有fianlize()方法的类的时侯按照下面的模式定义对象:
public class class1 : idisposable
{
public class1()
{
}
~class1 ()
{
//垃圾回收器将调用该方法,因此参数需要为false。
dispose (false);
}
//该方法定义在idisposable接口中。
public void dispose ()
{
//该方法由程式调用,在调用该方法之后对象将被终结。
//因为我们不希望垃圾回收器再次终结对象,因此需要从终结列表中去除该对象。
gc.suppressfinalize (this);
//因为是由程式调用该方法的,因此参数为true。
dispose (true);
}
//任何和回收相关的工作都由该方法完成
private void dispose(bool disposing)
{
lock(this) //避免产生线程错误。
{
if (disposing)
{
//需要程式员完成释放对象占用的资源。
}
//对象将被垃圾回收器终结。在这里添加其他和清除对象相关的代码。
}
}
}
导入论坛
引用链接
收藏
分享给好友
管理
举报
TAG: