.NET的GC机制和GC知识学习

在刚刚完成的一套.net笔试题卷子中,出了这样一道题:

面有关垃圾收集(GC)机制的说法中正确的是:

  • A. 值类型的对象会被分配到Stack上,而引用类型的对象会被分配到Large Object Heap上。
  • B. 使用try/catch/finally在效率上并不优于使用using的方式。
  • C. 可以实现自定义引用类型的Finalize方法,但它不能被重载。
  • D. GC的垃圾回收动作可以在代码中手动调用。

答案是BCD,A中错误在于引用对象会被默认分配到GC Heap的SOH(Small Object Heap)上(update:错误不仅如此,请参考打自己脸——有关.net对象分配),超过85KB对象会被分配到LOH。至于85K这个Magic Number的由来,应该是编写.Net框架的工程师们经过平时各种使用场景和优化推算出来的,我们不必多费心研究。

那么GC Heap为什么要分两类呢?这要从SOH和LOH不同的垃圾清理机制说起。

.Net的GC机制

GC Heap

对于LOH来说,清理垃圾的过程等于“回收+归并”,即不仅要清理掉内存空间,还要将尚存的对象向着一个方向移动直到和之前的对象紧紧贴在一起[1][3]。如下所示,SOH上有四个对象:

[obj1][    obj2     ][  obj3  ][obj4]
↑
0代指针(p0)

经过一次清理后,obj2和obj4没了,留下来的都是1代对象,并将0代指针移动到内存低位的第一个空闲空间处:

[obj1][  obj3  ][obj5]
↑               ↑
p1              p0

2代对象的生成类似,这样的移动保证不会产生大量的内存碎片,最大程度提高了内存使用效率。

可惜对于大对象来说,移动的成本过高,所以会优先采用查找可用剩余碎片空间的方式,将可以容纳新对象的空间重复利用。类似上面的内存情况在0代清理、新分配了obj5后会变成:

[obj1][obj5]         [  obj3  ]

obj5和obj3之间的内存如果小于85K就很可惜,因为再也利用不起来了。

不过需要注意的是,虽然大对象目前不会被归并处理,但不保证未来的CLR版本会不会实现一个LOH也进行存留对象移动的版本,所以希望保持现在这种行为的话,需要在变量前加fixed关键字进行标识。

分代处理

上文提到的0、1代是.Net中采用的分代垃圾回收机制的体现。目前.Net的GC中支持的代数为2(GC.MaxGeneration),一般来说,可以将0-2这三代对象视作:

  • 0代,大多数对象,GC进行回收时候都会被处理掉
  • 2代,少数常驻内存的对象,例如asp.net和整个网站生存期等同的一些全局对象
  • 1代,介于0代和2代之间的一个灰色地带。

在GC动作的时候,0代会首先会被回收,如果需要的内存空间不够用再依次处理1和2代对象。

对于程序中的引用对象,可以通过GC.GetGeneration()获取它的代数,这个方法还有一个重载版本支持WeakReference。

Read more >>>