C#垃圾回收

.NET中引用类型是分配在托管堆上的,用户只需要分配,而不需要释放。当一个对象不再被引用时,垃圾回收会在合适的时机释放对象。

控制垃圾回收

可以通过System.GC类控制垃圾回收。

Collect方法可以强制进行一次垃圾回收。

一般不进行强制垃圾回收,对性能有负面影响。常见的是在内存测试时使用。

.NET的垃圾回收是分代的,分代的基本思想是:越新分配的对象生存期可能越短,越可能被回收;越早分配的对象生存期越长,越不可能被回收;回收1代比回收全部更快。

目前.NET分为3代,第0代,第1代,第2代。可以通过GC.MaxGeneration获取最大代数,目前的值是2

当新分配对象时,会在第0代分配,如果内存不足,则会回收第0代中不被引用的对象,还被引用对象会提升到第1代,下一代内存如果还不足,会继续此操作。

一般第0代的内存很小,小到可以放到CPU缓存。

第2代的空间很大。

终结器

在进行垃圾回收时,如果对象实现了终结器,那么对象不能被立刻回收,回收程序还要调用对象的终结器。

确保终结

.NET代码是即时编译的,在极端情况,可能没有足够的内存编译终结器的代码,导致无法调用终结器。

通过继承System.Runtime.ConstrainedExecution.CriticalFinalizerObject类,用来确保终结器被编译。

资源句柄基类SafeHandle继承自上述类。

非托管资源的释放

垃圾回收只能够释放托管资源,对于非托管的资源,一般要实现IDispose接口,并同时实现终结器。这种模式就是Dispose模式。

实现IDispose接口的类型可以通过using语句来释放资源。

Dispose模式

        public class DisposableObject : IDisposable
        {
            private bool disposed = false;

            //子类可以重写来释放自己的资源
            protected virtual void Dispose(bool disposing)
            {
                if (!disposed)
                {
                    if (disposing)
                    {
                        //释放托管对象,即其他实现Dispose模式的对象
                    }

                    //释放未托管的资源,例如文件句柄

                    disposed = true;
                }
            }

            ~DisposableObject()
            {
                Dispose(false);
            }

            public void Dispose()
            {
                Dispose(true);
                //取消终结器。
                GC.SuppressFinalize(this);
            }
        }

实现Dispose模式的对象可以做到:

终结器是非托管资源资源被回收的最后一道保证,但其执行时机无法控制,对效率有负面影响,一般不要使用。

始终通过using语句来释放非托管资源。

资源所有权

一个对象只有拥有一个资源的所有权,才能释放资源。


var stream = File.OpenWrite("file.txt");

using(var writer = new StreamWriter(stream)){
    writer.WriteLine("hello");
}

默认情况,通过流创建StreamWriter对象时,StreamWriter对象会获得流的所有权,这样在StreamWriter释放时,其所拥有的流也被释放。流被释放后,后续不能被继续操作。


var stream = File.OpenWrite("file.txt");

using(var writer = new StreamWriter(stream,Encoding.UTF8,1024,true)){
    writer.WriteLine("hello");
}

using(var writer = new StreamWriter(stream)){
    writer.WriteLine("world");
}

通过设置leaveOpen参数为trueStreamWriter对象便不拥有流,释放时不会释放流,后续可以继续操作流。

始终清楚对象是否被其他对象拥有,如果被其他对象拥有,就不能通过对象自身释放它,而要通过所有者释放;如果没有被其他对象拥有,其他对象就不能释放它,而要其自身释放。