【译】让垃圾回收器高效工作(三)

这篇文章我们谈谈固定对象的内存地址(pinning)和弱引用……这两个和垃圾回收处理密切相关的东西。

固定对象的内存地址:

固定对象的内存地址和实现Finalize方法的对象有一点是相同的 …… 两者都是因为我们的程序不得不和本地代码打交道。

怎么固定对象的内存地址呢?有三种方法
1. 使用GCHandle的静态方法Alloc(object val,GCHandleType type) ,将type值设为GCHandleType.Pinned
2. 在C#中使用fixed关键字
3. 在调用本地代码时,本本地代码固定(例如:to marshal LPWSTR as a String object, Interop pins the buffer for the duration of the call)

对于小对象堆来说,在代码中固定对象地址是导致内存碎片的唯一原因,如果没有固定内存地址的对象那么在小对象堆中就不应该有碎片。

而对于大对象堆,固定内存地址的操作是无效的,因为现在的垃圾回收机制是不会移动大对象堆的对象的。不过,这一点只是GC的内部实现,你不应该依赖于这个实现,如果大对象需要固定内存地址,你还是要写固定需要的代码。

内存碎片从来都不是一个好东西。它会增加垃圾回收工作的难度 —— 如果没有固定内存地址的对象,垃圾回收器在移动内存时只需要将非垃圾对象覆盖空闲内存就可以了,而堆上存在固定地址的对象,垃圾回收器就不得不在移动中考虑,不覆盖这类对象,也不能移动它们。

那么如何才能知道你的程序中有多少内存碎片呢?你可以使用!dumpheap命令:“dumpheap –type Free -stat”会给出所有释放对象占用内存的统计信息。 通常情况下如果碎片大小占总大小的比例小于10%的话,就没什么可担忧的。因此如果你看到释放对象的绝对数很大,但是总数少于10%就没必要害怕。

如果你确实需要固定对象的地址,请注意下面几件事情:

1. 短时间的固定开销会很小
“短时间”,多短算短呢? 如果固定内存地址的对象在垃圾回收之前就成为垃圾对象了,那么这个时间就是足够短了。因为固定内存其实就是在对象头置一个bit位,如果在对象存活期没有发生垃圾回收,那么就没有额外开销。如果在垃圾回收发生后这个对象还活着,垃圾回收器在移动内存时就得做更多的计算保证不会移动此对象,也不会覆盖它。

2. 固定老对象的代价会比固定年轻对象的代价要小一些
何为“老对象”呢?是指经过两次垃圾回收,已经被迁移到2代堆的对象;这时候对象的所在的内存区域已经相对稳定了。造成内存碎片的可能性会小一些。


两个固定对象内存地址好实践:
1. 在大对象堆上分配固定地址的对象,每次使用使使用其中的一部分
这样做的优点是显而易见的,大对象堆不会做内存移动操作,所以就不存在因为固定对象地址导致的开销了;缺点是没有现成的API来把大对象分成一小块一小块使用,这需要开发人员按需编码使用。

2. 分配一个小对象的缓冲池,(and then hand them out when needed)
例如,我有一个缓冲池,方法M有一个byte[]数组需要固定内存地址。如果这个数组已经是2代对象了,就固定它。而如果缓冲区不需要使用很长时间,那么就在0代和1代回收时回收它。这样所有在缓冲池中的对象就都是2代对象了。
void M(byte[] b)
{
if (GC.GetGeneration(b) == GC.MaxGeneration)
{
RealM(b);
return;
}

// GetBuffer will allocate one if no buffers
// are available in the buffer pool.
byte[] TempBuffer = BufferPool.GetBuffer();
RealM(TempBuffer);
CopyBackToUserBuffer(TempBuffer, b);
BufferPool.Release(TempBuffer);
}

弱引用:

弱引用是如何实现的呢?

一个弱引用对象有托管和非托管两个部分。托管部分是WeakReference对象本身。在它的构造函数中我们创建一个GC句柄,它是非托管的部分 —— 这会在AppDomain的句柄表中插入一项(GCHandle类的Alloc方法都是这么做的,只不过是将不同类型插入到各自的表中)。当弱引用指向的对象没有强引用时就会被垃圾回收器回收掉。因为WeakReference对象本身是一个托管对象,所以它没有强引用时也会被回收。

弱引用没必要引用小对象:

如果你有一个非常小的对象,比如说一个DWORD字段,对象的大小是12byte(12byte是对象的最小尺寸)。而WeakReference对象有一个IntPtr和一个bool字段,而GC句柄是一个指针的大小(32位机器4byte,64位机器8byte);这就是说你需要使用15个byte的对象来延长一个12byte对象的长度,这是不划算的。显然你不应该创建很多弱引用对象来引用一些小对象。

那么,弱引用有什么用呢?
弱引用的作用是在垃圾回收发生之前即便对象上没有强引用,也可以再次使用该对象。如果执行垃圾回收它会被回收。

为什么要使用弱引用来跟踪一个对象的释放,而不是用Finalizer方法呢? 使用弱引用的优点是跟踪的对象不会被推迟到下次垃圾回收时才真正的被回收;缺点是需要消耗一点内存,只有当用户代码检查弱引用指向的对象为null时才清除对象。

下面是两个弱对象的使用实例:
Option A):

class A
{
  WeakReference _target; 

  MyObject Target
  {
     set
    {
       _target = new WeakReference(value);
    }
     get
    {
       Object o = _target.Target;
       if (o != null)
       {
           return o;
       }
       else
       {
          // my target has been GC'd - clean up
          Cleanup(); 
          return null;
       }
    }
}

void M()
{
   // target needs to be alive throughout this method.
  MyObject target = Target;

   if (target == null)
   // target has been GC'd, don't bother
   return;
   else
   {
      // always need target to be alive.
      DoSomeWork();
   }

    GC.KeepAlive(target);
}
}

Option B):

class A
{
    WeakReference _target;
    MyObject ShortTemp;
 
    MyObject Target
    {
        set
        {
            _target = new WeakReference(value);
        }
 
        get
        {
            Object o = _target.Target;
            if (o != null)
            {
                return o;
            }
            else
            {
                // my target has been GC'd - clean up
                Cleanup();       
                return null;
            }
        }
    }
 
    void M()
    {
        // target needs to be alive throughout this method.
        MyObject target = Target;
        ShortTemp = target;
 
        if (target == null)
        {
            // target has been GC'd, don't bother
            return;
        }
        else
        {
            // could assert that ShortTemp is not null.
            DoSomeWork();
        }
 
        ShortTemp = null;
    }
}

使用弱对象维护缓存:

你可以创建一个WeakReference指向对象的数组
WeakReferencesToObjects WeakRefs[];

数组中的每个元素都是弱对象指向的对象。我们可以定期的遍历这个数组看哪个对象已经死了然后释放引用此对象的WeakReference对象。

如果我们经常处理已经释放的对象,缓存就会在每次垃圾回收之后失效。

如果对你来说这还不够,你可以使用二级缓存机制。

维持一段时间的缓存项的强引用列表,在这段时间之后,将强引用转换成弱引用,弱引用意味着这些项将要被剔除。

你可能有不同的策略来处理缓存,比如根据缓存被读取次数——读取次数少的项被转换成弱引用;或者根据缓存项的个数,如果缓存项数超过某个数字就把超过的缓存项设置为弱引用。这取决于你的应用。调优缓存是另一个完整的主题——或许将来我会写一下。

原文:http://blogs.msdn.com/b/maoni/archive/2004/12/19/327149.aspx
原作者:Maoni Stephens

相关随笔:

让垃圾回收器高效工作(二)

让垃圾回收器高效工作(一)

.Net 垃圾回收机制原理(一)

.Net 垃圾回收机制原理(二)

.Net 垃圾回收和大对象处理 

参考:
使用GCHandle固定内存
http://www.codeproject.com/KB/cs/PinnedObject.aspx
http://msdn.microsoft.com/en-us/library/83y4ak54.aspx

我的微博地址是:http://weibo.com/yukaizhao 我会把一些技术心得碎片写到微博中,欢迎关注。
标签: gc, 垃圾回收
posted @ 2011-11-30 08:50 玉开 阅读(1462) 评论(10) 编辑 收藏

 回复 引用 查看   
#1楼[楼主]2011-11-30 09:19 | 玉开      
如果有问题,尽管问,我对垃圾回收的理解还可以。
 回复 引用 查看   
#2楼2011-11-30 11:33 | toEverybody      
可是在Win8以后,微软将对NET的类库及编译逐步实现本机代码的编译,也就是垃圾回收的机制也将会到尽头了...
 回复 引用 查看   
#3楼[楼主]2011-11-30 11:41 | 玉开      
@toEverybody
一方面,编译成本机代码不见得就会不用垃圾回收,因为在有些c++库里面也实现了垃圾回收
另一方面,win8出现了,但是公司的服务器在n年之内是不会迁移到win8的,因为需要银子呀。

 回复 引用 查看   
#4楼2011-11-30 11:58 | toEverybody      
在C++中很少用垃圾回收,也许与NET的回收方式不同(我没有了解过),在一些高性能的应用中,尽管C++有智能指针类的管理内存方式,但会影响性能,我想微软很早就想用垃圾回收的机制来开发OS,Office但都没有成功,充分说明了这一点...再说NET的垃圾回收是自动的,它背后如何工作的,编译器如何处理的,也许我们只是一种猜测...我想说明的是楼主不要在这些方面化时间..把精力用在其它有意义的方面,,,人生对一个人来说很短暂的.....就象我们有些朋友一开始选择了学Foxpro到Vfp9,最后什么结果呢??是不是浪费了青春了呢?如果选择了C语做嵌入式的话,是不是象医生一样越老越来钱呢>>呵呵...人个观点,楼主不要介意
 回复 引用 查看   
#5楼[楼主]2011-11-30 12:16 | 玉开      
@toEverybody
谢谢你善意的提醒,不过在我实际工作中已经证明了解这些东西是有意义的。

另外微软也有很多项目在用.Net开发,从他们的博客中可以看出他们是会注意托管内存使用情况的。

不要轻易的否定一个东西没用。

 回复 引用 查看   
#6楼2011-11-30 12:33 | toEverybody      
恩...挺执著的,只能用时间来检验了...要记住我说的喔
 回复 引用 查看   
#7楼2011-12-03 20:46 | toEverybody      
去了解一个WinRT,NET被边缘化了,由于性能问题,有可能CLR会被WinRT代替的
 回复 引用 查看   
#8楼[楼主]2011-12-04 11:28 | 玉开      
@toEverybody
我怎么觉得这个消息对于.net程序员是个好消息呢,意味着.net程序员可以直接编写出在winrt上运行的程序。

winrt的运行时有很多地方借鉴了.net的clr

 回复 引用 查看   
#9楼2011-12-04 11:43 | toEverybody      
@玉开
开发WinRT,一是用本机C++,一是用托管。。。
对NET的性能也不知有没有帮助,如果提升不知从哪里可以看出来。。。
软件莫非就是性能与复用吧。。。众观微软多种技术并行的时候,其中一种技术就是将要退出的时候

 回复 引用 查看   
#10楼[楼主]2011-12-04 12:27 | 玉开      
@toEverybody
不一定会有一种技术会推出。各有各的用处
c语言这么多年了,牛逼的c程序员还是很吃香。
java和c++比性能是必然比不上的,但是java在企业开发方面却做的很好。
.net也不会退出

发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 2268543 iM6OTpG80Ys=