步步为营 C# 技术漫谈 四、垃圾回收机制(GC) 下

  当你用Dispose方法释放未托管对象的时候,应该调用GC.SuppressFinalize。如果对象正在终结队列(finalization queue),GC.SuppressFinalize会阻止GC调用Finalize方法。因为Finalize方法的调用会牺牲部分性能。如果你的Dispose方法已经对委托管资源作了清理,就没必要让GC再调用对象的Finalize方法(MSDN)。附上MSDN的代码,大家可以参考.

public class BaseResource : IDisposable

{

    // 指向外部非托管资源

    private IntPtr handle;

    // 此类使用的其它托管资源.

    private Component Components;

    // 跟踪是否调用.Dispose方法,标识位,控制垃圾收集器的行为

    private bool disposed = false; 

    // 构造函数

    public BaseResource()

    {// Insert appropriate constructor code here. } 

    // 实现接口IDisposable.不能声明为虚方法virtual.子类不能重写这个方法.

    public void Dispose()

    {

        Dispose(true);

        // 离开终结队列Finalization queue,设置对象的阻止终结器代码

        GC.SuppressFinalize(this);

    } 

    // Dispose(bool disposing) 执行分两种不同的情况.

    // 如果disposing 等于 true, 方法已经被调用

    // 或者间接被用户代码调用. 托管和非托管的代码都能被释放

    // 如果disposing 等于false, 方法已经被终结器 finalizer 从内部调用过,

    // 你就不能在引用其他对象,只有非托管资源可以被释放。

    protected virtual void Dispose(bool disposing)

    {

        // 检查Dispose 是否被调用过.

        if (!this.disposed)

        {

            // 如果等于true, 释放所有托管和非托管资源 

            if (disposing)

            {

                // 释放托管资源.

                Components.Dispose();

            }

            // 释放非托管资源,如果disposing为 false, // 只会执行下面的代码.

            CloseHandle(handle);

            handle = IntPtr.Zero;

            // 注意这里是非线程安全的.

            // 在托管资源释放以后可以启动其它线程销毁对象,

            // 但是在disposed标记设置为true前

            // 如果线程安全是必须的,客户端必须实现。 

        }

        disposed = true;

    }

    // 使用interop 调用方法 

    // 清除非托管资源.

    [System.Runtime.InteropServices.DllImport("Kernel32")]

    private extern static Boolean CloseHandle(IntPtr handle); 

    // 使用C# 析构函数来实现终结器代码

    // 这个只在Dispose方法没被调用的前提下,才能调用执行。

    // 如果你给基类终结的机会.

    // 不要给子类提供析构函数.

    ~BaseResource()

    {

        // 不要重复创建清理的代码.

        // 基于可靠性和可维护性考虑,调用Dispose(false) 是最佳的方式

        Dispose(false);

    } 

    // 允许你多次调用Dispose方法,

    // 但是会抛出异常如果对象已经释放。

    // 不论你什么时间处理对象都会核查对象的是否释放, 

    // check to see if it has been disposed.

    public void DoSomething()

    {

        if (this.disposed)

        {

            throw new ObjectDisposedException();

        }

    } 

    // 不要设置方法为virtual.

    // 继承类不允许重写这个方法

    public void Close()

    {

        // 无参数调用Dispose参数.

        Dispose();

    } 

    public static void Main()

    {

        // Insert code here to create

        // and use a BaseResource object.

    }

}

GC.Collect() 方法

作用:强制进行垃圾回收。

GC的方法:

名称

说明

Collect()

强制对所有代进行即时垃圾回收。

Collect(Int32)

强制对零代到指定代进行即时垃圾回收。

Collect(Int32, GCCollectionMode)

强制在 GCCollectionMode 值所指定的时间对零代到指定代进行垃圾回收。


GC注意事项:

1、只管理内存,非托管资源,如文件句柄,GDI资源,数据库连接等还需要用户去管理

2、循环引用,网状结构等的实现会变得简单。GC的标志也压缩算法能有效的检测这些关系,并将不再被引用的网状结构整体删除。

3、GC通过从程序的根对象开始遍历来检测一个对象是否可被其他对象访问,而不是用类似于COM中的引用计数方法。

4、GC在一个独立的线程中运行来删除不再被引用的内存

5、GC每次运行时会压缩托管堆

6、你必须对非托管资源的释放负责。可以通过在类型中定义Finalizer来保证资源得到释放。

7、对象的Finalizer被执行的时间是在对象不再被引用后的某个不确定的时间。注意并非和C++中一样在对象超出声明周期时立即执行析构函数

8、Finalizer的使用有性能上的代价。需要Finalization的对象不会立即被清除,而需要先执行Finalizer.Finalizer不是在GC执行的线程被调用。GC把每一个需要执行Finalizer的对象放到一个队列中去,然后启动另一个线程来执行所有这些Finalizer.而GC线程继续去删除其他待回收的对象。在下一个GC周期,这些执行完Finalizer的对象的内存才会被回收。

9、.NET GC使用"代"(generations)的概念来优化性能。代帮助GC更迅速的识别那些最可能成为垃圾的对象。在上次执行完垃圾回收后新创建的对象为第0代对象。经历了一次GC周期的对象为第1代对象。经历了两次或更多的GC周期的对象为第2代对象。代的作用是为了区分局部变量和需要在应用程序生存周期中一直存活的对象。大部分第0代对象是局部变量。成员变量和全局变量很快变成第1代对象并最终成为第2代对象。

10、GC对不同代的对象执行不同的检查策略以优化性能。每个GC周期都会检查第0代对象。大约1/10的GC周期检查第0代和第1代对象。大约1/100的GC周期检查所有的对象。重新思考Finalization的代价:需要Finalization的对象可能比不需要Finalization在内存中停留额外9个GC周期。如果此时它还没有被Finalize,就变成第2代对象,从而在内存中停留更长时间。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章