1、Finalize方法
Finalize方法要做的事情通常是回收垃圾回收器不能回收的资源,例如文件句柄,数据库连接(非托管资源的释放)
(1)第一次垃圾回收
对象B、E、G、H、I和J被标记为垃圾--->添加到Freachable队列:
(1)没有Finalize方法,直接回收
(2)有Finalize方法,一个独立的线程finalizer thread异步进行处理调用finalize method
(2)再一次垃圾回收
实现Finalize方法的对象才被真正的回收。这些对象的Finalize方法已经执行过了,Freachable队列也清空了。
整个过程中,需要两次垃圾回收才能收回这些对象占有的内存。实际情况下可终结对象会被提升代,所以可能需要不止两次回收才能收回这些对象占有的内存。
这类资源,垃圾回收器在清理的时候会调用Object.Finalize()方法。默认情况下,方法是空的,对于非托管对象,需要在此方法中编写回收非托管资源的代码,以便垃圾回收器正确回收资源。
在.NET中,Object.Finalize()方法是无法重载的,编译器是根据类的析构函数来自动生成Object.Finalize()方法的,所以对于包含非托管资源的类,可以将释放非托管资源的代码放在析构函数。
备注:
(1)提供析构函数,避免资源未被释放,主要是指非内存资源;
(2)对于Dispose和Close方法来说,需要看所定义的类型所使用的资源(参看前面所说),而决定是否去定义这两个函数;
(3)在实现Dispose方法的时候,一定要加上“GC.SuppressFinalize( this )”语句,避免再让GC调用对象的析构函数。
/// <summary>
/// The class to show three disposal function
/// </summary>
public class DisposeClass:IDisposable
{
public void Close()
{
Debug.WriteLine( "Close called!" );
}
~DisposeClass()
{
Debug.WriteLine( "Destructor called!" );
}
#region IDisposable Members
public void Dispose()
{
// TODO: Add DisposeClass.Dispose implementation
Debug.WriteLine( "Dispose called!" );
GC.SuppressFinalize( this );
}
#endregion
}
| 析构函数 | Dispose方法 | Close方法 |
意义 | 销毁对象 | 销毁对象 | 关闭对象资源 |
调用方式 | 不能被显示调用,会被GC调用 | 需要显示调用 或者通过using语句 | 需要显示调用 |
调用时机 | 不确定 | 确定,在显示调用或者离开using程序块 | 确定,在显示调用时 |
2、线程劫持与工作站模式:
当GC开始内测回收的时候,所有线程都必须挂起,因为这些线程不能再访问内存中的对象。(这是因为GC线程要压缩内存,压缩过程中,对象的引用可能变得无效,所以只能在GC完成后,其他线程才能继续执行。)
等待线程进入一个安全点,GC就会开始生效,CLR会对其他线程进行劫持,所有这些线程都进入了同一个方法,并且都试图获取同一个临界区。
工作站模式(Workstation):并发方式、非并发方式(阻塞式GC)、后台GC模式(.net4.0后)
服务器模式(Server):非并发方式(Non-concurrent)、后台GC模式(.net4.5后)
默认为工作站模式的并发方式,并且总是运行在单核的CPU上(单核CPU有且仅能工作站模式)
<configuration>
<runtime>
<gcServer enabled="true|false"/>
<gcConcurrent enabled="true|false"/>
</runtime>
</configuration>
工作站模式工作情况:
无论是在工作站模式还是在服务器模式上,只要是非并发方式都称为阻塞式GC。因为这种方式下,GC运行的时候,都会挂起对应CPU上的所有托管线程,然后清理内存完毕后,才恢复挂起的内存。在这期间,其他的代码是无法运行的。
开启了并发GC的情况下:
由于第0代和第1带的回收非常的快(毫秒级),所以在第0代和第1代上的回收是不做并发的,只有在第2代回收时才会并发。当第2代回收的时候,有一个专门的GC线程去做(和其他托管线程并发运行),其他托管线程只能在第0代和第1代上分配空间。
服务器模式下:每个CPU有一个线程专门执行GC作业,并且托管的堆也会被分成好几个区域,每个线程回收它自己的托管堆,并且这些线程都是最高优先权的,这些线程都只做GC操作,这就使得GC速度很快,但也意味着用户线程会阻塞。服务器模式适合服务器应用程序,只关注高的响应率。
在.NET4.0的时候,并发模式的升级版,并且默认是开启的,这种后台方式将会替代并发模式的。
后台GC可以同时在第0、1、2代上执行。这时第0、1 代上的垃圾收集被称为前台GC(即阻塞式GC)。后台GC,则可以启动另一个临时GC,与并发GC相同,后台GC只运用于完全的垃圾回收(第0、1、2代)并工作在独立的GC线程中。
3、弱引用
弱引用,指的是一个变量可以被垃圾回收了。这个对象不会用了,但是日后又担心会用,而且这个对象的创建非常耗时,我们就要用弱引用,需要注意的是,弱引用引用起来的对象是可以被垃圾回收的,但只要没被垃圾回收(能引用得到),就可以继续使用。
Person p = new Person() { ID = 1,Name = "张三",BirthDate = DateTime.Now }; //对象初始化器
WeakReference wReference = new WeakReference(p); //将p对象弱引用起来
p = null;
//手动调用垃圾回收
//GC.Collect();
object o = wReference.Target;
if(o != null && wReference.IsAlive) {
Person p1 = o as Person;
Console.WriteLine(p1.ID + "==" + p1.Name + "==" + p1.BirthDate);
} else {
Console.WriteLine("对象被垃圾回收了,请重新创建对象吧");
}
虽然将p指向了null,但是因为还没被回收,我们通过弱引用(WeakReference)能找到它,便可继续使用。【注释GC.Collect()】
弱引用的适用场景:
1.创建一个对象非常耗时;
2.这个对象不会用了(可以被垃圾回收),但是日后又担心会用;
3.弱引用的对象也不是一定就存在,如果手动调用GC.Collect()或者正好被垃圾回收,也会引用不到,此时若要用,只能重新创建对象。
4、代龄与分代算法
.NET将heap分成3个代龄区域: Gen 0、Gen 1、Gen 2
垃圾回收是在第0代满的时候发生的。使用代(generation)的机制的唯一目的就是提高性能。基本思路是,第0代是最近分配的对象,从未被垃圾回收算法检查过。在一次垃圾回收中存活下来的对象被提升到另一代。如经过一次垃圾回收,第0代被提升为第1代;第1代被提升为第2代。
第0代:局部变量
第1代(通常为2 MB)是经过0代垃圾收集后仍然驻留在内存中的对象,它们通常是表单,按钮等对象
第2代是经历过几次垃圾收集后仍然驻留在内存中的对象,它们通常是一些应用程序对象
(1)如果Gen 0 heap内存达到阀值,则触发0代GC,0代GC后Gen 0中幸存的对象进入Gen 1。
(2)如果Gen 1的内存达到阀值,则进行1代GC,1代GC将Gen 0 heap和Gen 1 heap一起进行回收,幸存的对象都进入Gen 2。
(3)2代GC将Gen 0 heap、Gen 1 heap和Gen 2 heap一起回收
内存吃紧时(例如0代对象充满),GC便被调入执行引擎——也就是CLR——开始对第0代的空间进行标记与压缩工作、回收工作。特别的,当对第2代回收后任然无法获得足够的内存,那么系统就会抛出OutOfMemoryException
当经过几次GC过后,0代中的某个对象仍然存在,那么它将被移动到第1代。同理,第1、2代也按同样的逻辑运行。GC Heap中代的数量与容量,都是可变的。
下面的示例强制的第 2 代对象进行垃圾回收Optimized设置。
GC.Collect(2, GCCollectionMode.Optimized);
备注:Optimized是一个枚举值,指定垃圾回收是强制进行(GCCollectionMode.Default 或 GCCollectionMode.Forced)还是优化 (GCCollectionMode.Optimized)。