C#GC

  1. C#gc

根:类中定义的任何静态字段,方法的参数,局部变量(仅限引用类型变量)等都是根,另外cpu寄存器中的对象指针也是根 。根是在堆之外可以找到的各种入口点。

对象可达与不可达:如果一个根引用了堆中的一个对象,则该对象为可达,否则是不可达。

垃圾回收的基本原理:回收分为两个阶段:标记和压缩,标记的过程就是判断对象是否可达的过程。当所有的根检查完毕后,堆中将包含可达(已标记)与不可达(未标记)对象。

标记完成后,进入压缩阶段。在这个阶段中,垃圾回收器线性遍历堆,以寻找不可达对象的连续内存块。并把可达对象移动到这里以压缩堆。这个过程有点类似于磁盘空间的碎片整理。

不可达对象清除后,移动可达对象实现内存压缩。

压缩之后,指向这些对象的指针和cpu寄存器都会失效,垃圾回收器必须重新访问所有根,并修改他们来指向对象的新内存位置。这会造成显著的性能损失。这个损失也是托管堆的主要缺点。

垃圾回收算法-分代算法

代是CLR垃圾回收器采用的一种机制,他唯一的目的就是提升应用程序的性能。分代回收,速度显然快于回收整个堆。

CLR托管堆支持3代:0,1,2。第0代的空间约为256kb,第一代约为2m,第二代约为10m。新构造的对象会被分配到第0代。

当第0代空间满时,垃圾回收器启动回收,不可达对象会被回收,存活的对象会被归于第1代。

当第0代空间已满,第1代也开始有很多不可达对象以至空间将满时,这时两代垃圾都将被回收。存活下来的可达对象,第0代升为第1代,第1代升为第2代。

实际CLR的代回收机制更加智能,如果新创建的对象生存周期很短,第0代垃圾也会立刻被垃圾回收器回收(不用等空间分配满)。另外,如果回收了第0代,发现还有很多对象可达,并没有释放多少内存,就会增大第0代的预算至512kb,回收效果转变为:垃圾回收的次数将减少,但每次都会回收大量的内存。如果还没有释放多少内存,垃圾回收将执行完全回收(3代),如果还不够,则会抛出内存异常。

也就是说,垃圾回收器会根据回收内存的大小,动态调整每一代的分配空间预算,达到自动优化。

.NET的GC机制有两个问题:

首先,GC并不是能释放所有的资源,它不能自动释放非托管资源。

第二,GC并不是实时性的,这将会造成系统性能上的不确定性。所以有了IDisposable接口,它定义了Dispose方法,这个方法用来供程序员显式调用以释放非托管资源。使用using语句可以简化资源。

托管资源:托管资源指的是.NET可以自动进行回收的资源,主要是指托管堆上分配的内存资源。托管资源的回收工作是不需要人工干预的,有.NET运行库在合适调用垃圾回收器进行回收。

非托管资源:非托管资源指的是.NET不知道如何回收的资源,最常见的一类非托管资源是包装操作系统资源的对象,例如文件,窗口,网络连接,数据库连接,画刷,图标等。这类资源,垃圾回收器在清理的时候会调用Object.Finalize()方法。默认情况下,方法是空的,对于非托管对象,需要在此方法中编写回收非托管资源的类,可以将释放非托管资源的代码放在析构函数。

频繁调用GC.Collect()方法会降低程序的性能,当应用程序代码中某个确定的点上使用的内存量大量减少时,在这种情况下使用 GC.Collect 方法可能比较合适。

.NET提供了三种释放方法:

       第一种:提供Close方法:

               关闭对象资源,在显示调用时被调用。Close方法是不存在的,是使用者自己写的,正常情况下Close方法里面会调用Dispose()方法。

       第二种:Dispose

               继承IDisposable接口,实现Dispose方法,调用Dispose方法,销毁对象,需要显示调用或者通过using语句,在显示调用或者离开using程序块时被调用。

Dispose方法用于清理对象封装的非托管资源,而不是释放对象的内存,对象的内存依然由垃圾回收器控制。

Dispose方法调用,不但释放该类的非托管资源,还释放了引用的类的非托管资源。

Dispose模式就是一种强制资源清理所要遵守的约定;Dispose模式实现了IDisposable接口,从而使得该类型提供一个公有的Dispose方法。

第三种:析构函数(Finalize)

        一个正常情况的类是不会写析构函数的,而一旦一个类写了析构函数,就意味着GC会在不确定的时间调用该类的析构函数,判断该类的资源是否需要释放,然后调用finalize方法,如果重写了finalize方法则调用重写的finalize方法,Finalize方法的作用是保证.NET对象能在垃圾回收时清除非托管资源。

在.NET中Object.Finalize方法是无法重载的,编译器是根据类的析构函数来自动生成Object.Finalize方法的

        finalize由垃圾回收器调用;dispose由对象调用。finalize无需担心因为没有调用finalize而使非托管资源得不到释放,但因为由垃圾回收器管理,不能保证立即释放非托管资源;而dispose一调用就释放非托管资源。

  GC.Collect(); //强制对所有代进行即时垃圾回收
当应用程序代码中某个确定的点上使用的内存量大量减少时,在这种情况下使用 GC.Collect 方法可能比较合适。

实现模型: 
1、由于大多数的非托管资源都要求可以手动释放,所以,我们应该专门为释放非托管资源公开一个方法。实现IDispose接口的Dispose方法是最好的模型,因为C#支持using语句快,可以在离开语句块时自动调用Dispose方法。 

2、虽然可以手动释放非托管资源,我们仍然要在析构函数中释放非托管资源,这样才是安全的应用程序。否则如果因为程序员的疏忽忘记了手动释放非托管资源, 那么就会带来灾难性的后果。所以说在析构函数中释放非托管资源,是一种补救的措施,至少对于大多数类来说是如此。 

3、由于析构函数的调用将导致GC对对象回收的效率降低,所以如果已经完成了析构函数该干的事情(例如释放非托管资源),就应当使用SuppressFinalize方法告诉GC不需要再执行某个对象的析构函数。 

4、析构函数中只能释放非托管资源而不能对任何托管的对象/资源进行操作。因为你无法预测析构函数的运行时机,所以,当析构函数被执行的时候,也许你进行操作的托管资源已经被释放了。这样将导致严重的后果。 

5、(这是一个规则)如果一个类拥有一个实现了IDispose接口类型的成员,并创建(注意是创建,而不是接收,必须是由类自己创建)它的实例对象,则 这个类也应该实现IDispose接口,并在Dispose方法中调用所有实现了IDispose接口的成员的Dispose方法。 
只有这样的才能保证所有实现了IDispose接口的类的对象的Dispose方法能够被调用到,确保可以手动释放任何需要释放的资源。

https://www.jianshu.com/p/26522934afdd

原文地址:https://www.cnblogs.com/mcyushao/p/10632136.html