关于.NET垃圾回收(GC)的基本问题

.Net应用程序中很多问题都是没有正确的理解垃圾回收的工作原理而导致的,这里有一些关于GC的基本问题,如果看到问题答案心里都有数,那就请略过这篇,不然就一起来复习下吧。

  • 什么是代?
  • 什么时候发生垃圾回收?
  • 什么是大对象堆?
  • 什么是root?
  • 什么是finalizer
  • 什么是finalizequeue
  • 什么情况下会发生out ofmemory exception
  • 什么情况下要实现IDisposible接口?
  • 什么情况下用GC.Collect
  • GC有哪几种模式?

什么是代?

垃圾回收器使用了代的机制目的是提升性能,总共分为三代(Gen0, Gen1, Gen2)GC的工作机制基于以下假设,

  1. 对象越新,生存期越短
  2. 对象越老,生存期越长
  3. 回收堆的一部分比回收整个堆时间短

在应用程序的生命周期中,最近新建的对象被分配在第0代,在一次垃圾回收之后存活下来的进入下一代。这样可以使GC专注于回收最有可能存在更多可回收对象的第0(最近分配的最有可能很快被释放)

什么时候发生垃圾回收?

  1. 0代满
  2. 代码显示调用GC.Collect方法
  3. Windows报告内存不足 - CLR注册了 Win32 CreateMemoryResourceNotificationQueryMemoryResourceNotification监视系统总体内存使用情况,如果收到Window报告内存不足的通知,强行执行GC
  4. CLR卸载AppDomain
  5. CLR关闭

什么是大对象堆?

采用大对象堆是垃圾回收另外一个性能提升的策略,任何大于等于85000byte的对象都被视为大对象在特殊的大对象堆中分配。

大对象堆的回收策略

  1. 大对象堆被认为是第2代一部分,大对象堆回收时候同时回收第2
  2. 大对象堆不进行压缩操作-因为太耗时耗力

根据该策略我们可以推测如果大对象频繁的被分配将造成频繁的第2代垃圾回收(即完全垃圾回收),对性能造成较大影响。

什么是root?

静态对象

方法参数

局部变量

CPU寄存器

什么是finalizer?

大多数时候我们创建的类不包含非托管资源,因此只需要直接使用,CLR自然会判断其生命周期结束而后回收相应的托管资源。但如果我们创建了含有非托管资源的类,CLR提供了finalize机制来帮助自动释放非托管资源。

实现finalizer的语法与析构函数类似,实现了这个类似于析构函数的方式实际上被隐式转换成了重载父类的Finalize方法(object类默认提供了finalize方法)

class Car
{
    ~Car()  // destructor
    {
        // cleanup statements...
    }
}

转换后

protected override void Finalize()
{
    try
    {
        // Cleanup statements...
    }
    finally
    {
        base.Finalize();
    }
}

什么是finalizequeue?

在新建一个类的实例时,如果该类定义了Finalize方法,那么该类在构造器调用之前会将指向该对象的指针存放在一个叫finalization list中。垃圾回收时如果该对象被认定为垃圾,那么CLR会从finalizationlist中查找是否存在相应的对象指针,如果存在则将该指针移除,然后在freachable队列中加入该对象指针,CLR提供了一个高优先级的Finalizer线程来专门负责调用feachable队列中对象的finalize方法以释放资源。

什么情况下会发生out of memory exception?

在一个内存分配请求到达时,CLR发现第0代没有足够空间从而触发第0GC,如果还是没有足够的内存,CLR发起完全GC,接下来CLR尝试增大第0代大小,如果没有足够的地址空间来增大第0代大小或满足内存分配请求,就会抛出OutOfMemoryException

因此发生OutOfMemoryException的两个可能性是

  1. 虚拟地址空间耗尽
  2. 物理内存耗尽

什么情况下要实现IDisposible接口?

IDisposible最重要的目的是释放非托管资源,垃圾回收可以自动回收托管资源,但是对于程序中使用的非托管资源却一无所知,例如数据库连接,对象句柄等。

msdn中给了正确的IDisposable接口的正确实现,这个实现中最容易被误解的是protectedvirtualvoid Dispose(bool disposing)方法中布尔参数disposing的作用是什么。

参数disposing的目的是在显式调用Dispose方法或隐式调用Finalizer的情况下区别对待托管资源。在两种情况下对于非托管资源的处理是一致的,直接释放,不应该将非托管资源的释放放在if(disposing)的处理中。

为什么要区别对待托管资源?在显式调用dispose方法的时候可以保证其内部引用了托管资源未被回收,所有可以直接调用其相应的释放方法。但是finalizer被调用dispose的方法时,由于GC无法保证托管资源的释放顺序,所以在dispose方法中不应该再去访问内部的托管资源,有可能内部的托管资源已经被释放掉了。

using System;
using System.ComponentModel;
// The following example demonstrates how to create
// a resource class that implements the IDisposable interface
// and the IDisposable.Dispose method.
publicclass DisposeExample
{
    // A base class that implements IDisposable.
    // By implementing IDisposable, you are announcing that
    // instances of this type allocate scarce resources.
    publicclass MyResource: IDisposable
    {
        // Pointer to an external unmanaged resource.
        private IntPtr handle;
        // Other managed resource this class uses.
        private Component component = new Component();
        // Track whether Dispose has been called.
        privatebool disposed = false;
// The class constructor.
        public MyResource(IntPtr handle)
        {
            this.handle = handle;
        }
// Implement IDisposable.
        // Do not make this method virtual.
        // A derived class should not be able to override this method.
        publicvoid Dispose()
        {
            Dispose(true);
            // This object will be cleaned up by the Dispose method.
            // Therefore, you should call GC.SupressFinalize to
            // take this object off the finalization queue
            // and prevent finalization code for this object
            // from executing a second time.
            GC.SuppressFinalize(this);
        }
// Dispose(bool disposing) executes in two distinct scenarios.
        // If disposing equals true, the method has been called directly
        // or indirectly by a user's code. Managed and unmanaged resources
        // can be disposed.
        // If disposing equals false, the method has been called by the
        // runtime from inside the finalizer and you should not reference
        // other objects. Only unmanaged resources can be disposed.
        protectedvirtualvoid Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called.
            if(!this.disposed)
            {
                // If disposing equals true, dispose all managed
                // and unmanaged resources.
                if(disposing)
                {
                    // Dispose managed resources.
                    component.Dispose();
                }
// Call the appropriate methods to clean up
                // unmanaged resources here.
                // If disposing is false,
                // only the following code is executed.
                CloseHandle(handle);
                handle = IntPtr.Zero;
// Note disposing has been done.
                disposed = true;
}
        }
// Use interop to call the method necessary
        // to clean up the unmanaged resource.
        [System.Runtime.InteropServices.DllImport("Kernel32")]
        privateexternstatic Boolean CloseHandle(IntPtr handle);
// Use C# destructor syntax for finalization code.
        // This destructor will run only if the Dispose method
        // does not get called.
        // It gives your base class the opportunity to finalize.
        // Do not provide destructors in types derived from this class.
        ~MyResource()
        {
            // Do not re-create Dispose clean-up code here.
            // Calling Dispose(false) is optimal in terms of
            // readability and maintainability.
            Dispose(false);
        }
    }
    publicstaticvoid Main()
    {
        // Insert code here to create
        // and use the MyResource object.
    }
}

什么情况下用GC.Collect?

大多数情况下我们都应该避免调用GC.Collect方法,让垃圾回收器自动执行,但是还是有些情况比如在某个时刻会发生一次非重复性事件导致大量的对象死亡,这个时候我们可以不依赖于垃圾回收器的自动机制,手动调用GC.Collect方法。

记住不要为了改善应用程序相应时间而调用GC.Collect,而是应该处于减少工作集的目的。

GC有哪几种模式?

工作站模式(workstation mode)

这种模式下GC假设机器上运行的其他应用程序对CPU资源要求不高,并且该模式可以使用并发或非并发的模式运行。

并发模式是默认的工作站模式,该模式下垃圾回收器分配一个额外的后台线程在应用程序运行时并发回收对象。一个线程因为分配对象造成第0代超出预算时,垃圾回收器挂起所有线程,判断需要回收哪些代,如果需要回收第2代,就会增加第0代的大小。然后应用程序恢复执行。并发回收是为了给用户更好的交互体验,适合客户端应用程序,但是同时要注意并发回收对性能有损害,使用更多地内存。

禁用并发模式的配置为





服务器模式 (servermode)

这种模式下GC假设机器上没有运行其他应用程序,所有的CPU都可以用来进行垃圾回收操作。在这种情况下虚拟内存按照CPU数量划分区域分开对待,每个CPU上都运行一个GC线程负责回收自己的区域。

参考文档

Memory Management andGarbage Collection in the .NET Framework

http://msdn.microsoft.com/en-us/library/hh156531.aspx


你可能感兴趣的:(关于.NET垃圾回收(GC)的基本问题)