Flier's Sky

天空,蓝色的天空,眼睛看不到的东西,眼睛看得到的东西

导航

Whidbey 中对 GC 的功能两点增强

Posted on 2004-07-08 10:40  Flier Lu  阅读(735)  评论(0编辑  收藏  举报
Whidbey 中对 GC 的功能两点增强

http://www.blogcn.com/user8/flier_lu/index.html?id=1452116&run=.0CDCD50

    Brad Abrams在其blog中发表了一篇介绍Whidbey 中对 GC 的功能增强的文章Teaching an old dog new tricks: GC fun in Whidbey。其中提到两种对Unmanaged Resource的管理的GC增强。Justin Rogers则在其blog中回应前文,提出了一些很有趣的观点The new face of the GC in Whidbey... I'm not sure this is a pretty face...

     新增的GC.AddMemoryPressure函数和GC.RemoveMemoryPressure函数可以提示GC当前Unmanaged资源的使用情况,以便GC判断在合适的时候进行回收工作;以前供WinForm内部使用的HandleCollector类也公开允许用户使用,用于管理Unmanaged资源的自动回收。

     对于CLR/JVM这种使用垃圾回收机制的环境,如何处理其无法管理的外部资源类型是一件让人头痛的事情。例如在使用Win32的位图(BITMAP)资源时,在Managed对象中只需保存一个句柄,但实际上内存占用跟位图文件大小相关,同时句柄本身的数量也是受到系统本身限制的。而这些Unmanaged资源对GC来说都是不可见的,GC只能看到这座冰山露出水面的一小部分,其余的部分只能靠程序员自觉管理,如使用Dispose模式。但这样的使用就违背了GC的基本原则,必然会出现两种内存管理模型的冲突。Whidbey中新增的这两个GC的增强,实际上就是分别对这两种内存管理模型,提供了与GC兼容的接口。

     GC.AddMemoryPressure函数和GC.RemoveMemoryPressure函数是全局性统计的函数,它们必须被使用到Unmanaged资源的CLR对象成对地调用,以保障对资源使用情况的精确跟踪。如果一个对象忘记调用RemoveMemoryPressure函数,则此对象和其Unmanaged资源在被施放后,GC认为以前用AddMemoryPressure函数注册的Unmanaged资源仍在使用,会降低GC的准确率。因此最好使用mihailik给出的封装类UnmanagedResource,通过IDispose和Finalizer确保两个函数的匹配调用。
 

以下为引用:

 public abstract class UnmanagedResource : IDisposable
 {
   readonly int m_PressureAmount;

   public UnmanagedResource(int pressureAmount)
     : this( pressureAmount, true )
   { }


   public UnmanagedResource(int pressureAmount, bool addPressureNow)
   {
     this.m_PressureAmount=pressureAmount;

     if( addPressureNow )
       ResourceAllocated();
   }

   protected void ResourceAllocated()
   {
     GC.AddMemoryPressure(PressureAmount);
   }

   protected void ResourceReleased()
   {
     GC.RemoveMemoryPressure(PressureAmount);
   }


   protected int PressureAmount
   {
     get { return m_PressureAmount; }
   }


   public void Dispose()
   {
     Dispose(true);
   }

   protected virtual void Dispose(bool disposing)
   {
     ResourceReleased();

     if( disposing )
     {
       GC.SuppressFinalize();
     }
   }

   ~UnmanagedResource()
   {
     Dispose(false);
   }
 }
 



     不过个人认为这种提示的管理粒度过大了,而且过于依赖人的自觉性。不如使用IoC模式的思想,定义一个接口IUnmanagedResource,所有使用Unmanaged资源的类都实现此接口,然后GC提供GC.RegisterForUnmanagedResource函数将对象注册到GC。此接口提供GetUnmanagedResourceSize()函数,让GC了解其Unmanaged资源的使用情况,如
 
以下为引用:

 public interface IUnmanagedResource
 {
   uint GetUnmanagedResourceSize();
 };

 class Bitmap : IUnmanagedResource
 {
    private long _size;

    uint GetUnmanagedResourceSize()
    {
      return _size;
    }

    Bitmap (string path )
    {
       _size = new FileInfo(path).Length;

       GC.RegisterForUnmanagedResource(this);

       // other work
    }
 }
 



     这样的好处是可以将对Unmanaged资源的管理粒度降低到对象一级,并让CLR对象和Unmanaged资源绑定,确保施放CLR对象时能够同步更新整体资源使用情况的统计数据,代价是GC需要维护一个和Finalizer列表类似的UnmanagedResource列表。
     在实现上GC.AddMemoryPressure函数和GC.RemoveMemoryPressure函数更新由GC的一个子类MemoryWatcher维护的几个统计值,并根据一定的策略触发GC的回收条件。

     System.Runtime.InteropServices.HandleCollector实现则比较简单,构造函数中指定阈值,自身维护一套计数器,在超出指定范围后回收句柄。使用方法很简单,如下:
 

以下为引用:

 // HandleCollector(string name, int initialThreshold,  int maximumThreshold);

 class XXX
 {
   static readonly HandleCollector GdiHandleType =
      new HandleCollector( “GdiHandles”, 10, 50);

   static IntPtr CreateSolidBrush()
   {
      IntPtr temp = CreateSolidBrushImpl(…);

      GdiHandleType.Add();

      return temp;
   }

 

   internal static void DeleteObject(IntPtr handle)
   {
      DeleteObjectImpl(handle);

      GdiHandleType.Remove();
   }
 }
 



    有兴趣的朋友可以进一步看看这个例子:
     ::URL::http://www.gotdotnet.com/userfiles/chrisan/HandleCollector.zip