C#垃圾回收机制-GC
收集博客资料排版而成,非原创
C#资源类型
简单来说分为值类型和引用类型。前者是分配在栈上,并不需要GC回收;后者是分配在堆上,因此它的内存释放和回收需要通过GC来完成
- 托管资源
- 内存分配的资源
- 非托管资源
- Stream,数据库的连接,GDI+的相关对象,还有Com对象等
回收的复杂性
实际上对象有一个重要的特点导致无用对象判断的复杂性:对象间的相互引用!
如果没有相互引用,就可以通过“引用计数”这种简单高效的方式实现无用对象的判断,并实现实时回收。
正是由于相互引用的存在导致GC需要设计更为复杂的算法,这样带来的最大问题在于丧失了资源回收的实时性,而变成一种不确定的方式。
回收方法
.Net提供了三种方法,也是最常见的三种,大致如下:
- 析构函数,用于GC
- 继承IDisposable接口,实现Dispose方法;
- 提供Close方法。
Close与Dispose这两种方法的区别在于,
调用完了对象的Close方法后,此对象有可能被重新进行使用;
而Dispose方法来说,此对象所占有的资源需要被标记为无用了,也就是此对象被销毁了,不能再被使用。
例如,常见SqlConnection这个类,当调用完Close方法后,可以通过Open重新打开数据库连接,当彻底不用这个对象了就可以调用Dispose方法来标记此对象无用,等待GC回收
-
对于托管资源
.Net所指的托管仅仅针对内存(托管资源)的资源托管,系统提供GC-Garbage Collector机制,而至于其他资源则需要手动进行释放。
只要判定此对象或者其包含的子对象没有任何引用是有效的,那么系统就认为它是垃圾。
-
对于非托管资源的释放,C#提供了两种方式:
Finalizer:写法貌似C++的析构函数,本质上却相差甚远。
- Finalizer是对象被GC回收之前调用的终结器,初衷是在这里释放非托管资源,但由于GC运行时机的不确定性,通常会导致非托管资源释放不及时。
- 另外,Finalizer可能还会有意想不到的副作用,比如:被回收的对象已经没有被其他可用对象所引用,但Finalizer内部却把它重新变成可用,这就破坏了GC垃圾收集过程的原子性,增大了GC开销。
Dispose Pattern:C#提供using关键字支持Dispose Pattern进行资源释放。
- 这样能通过确定的方式释放非托管资源,而且using结构提供了异常安全性。
- 所以,一般建议采用Dispose Pattern,并在Finalizer中辅以检查,如果忘记显式Dispose对象则在Finalizer中释放资源。
回收的过程
系统为GC安排了独立的线程。那么GC的工作大致是,查询内存中对象是否成为垃圾,然后对垃圾进行释放和回收。对于GC对于内存回收采取了一定的优先算法进行轮循回收内存资源。
其次,对于内存中的垃圾分为两种,一种是需要调用对象的析构函数,另一种是不需要调用的。GC对于前者的回收需要通过两步完成,第一步是调用对象的析构函数,第二步是回收内存,但是要注意这两步不是在GC一次轮循完成,即需要两次轮循;相对于后者,则只是回收内存而已。
GC为了提高回收的效率使用了Generation的概念,原理如下:
- 第一次回收之前创建的对象属于Generation 0,之后,每次回收时这个Generation的号码就会向后挪一,也就是说,第二次回收时原来的Generation 0变成了Generation 1,而在第一次回收后和第二次回收前创建的对象将属于Generation 0。
- GC会先试着在属于Generation 0的对象中回收,因为这些是最新的,所以最有可能会被回收,比如一些函数中的局部变量在退出函数时就没有引用了(可被回收)。
- 如果在Generation 0中回收了足够的内存,那么GC就不会再接着回收了如果回收的还不够,那么GC就试着在Generation 1里回收内存,以此类推
所以GC回收内存的机制不是即时回收,是内存中存在一定数量的垃圾之后,GC将进行内存回收,直到回收到足够数量内存为止
补充
托管资源可以通过调用GC.Collect();来强制GC进行垃圾回收
非托管资源
public class AA:IDisposable//继承IDisposable,从而获得接口Dispose
{
FileStream fs = new FileStream("D://a.txt",FileMode.Open);
~AA()
{
MessageBox.Show("析构函数被执行了");
}
#region IDisposable 成员
public void Dispose()
{
fs.Dispose();
MessageBox.Show("dispose执行了");
GC.SuppressFinalize(this);//告诉GC,让它不用再调用对象的析构函数,
}
#endregion
}