在2代垃圾回收时,可以将不需要的内存通过VirtualFree交还给操作系统。交还的过程参见下图:

  什么时候回收大对象呢?

  在讨论什么时候回收大对象之前先来看下普通的垃圾回收操作什么时机执行吧。垃圾回收在下列情况下发生:

  1、申请的空间超过0代内存大小或者大对象堆的阀值,多数的托管堆垃圾回收在这种情况下发生

  2、在程序代码中调用GC.Collect方法时;如果在调用GC.Collect方法是传入GC.MaxGeneration参数时,会执行所有代对象的垃圾回收,包括大对象堆的垃圾回收

  3、操作系统内存不足时,当应用程序收到操作系统发出的高内存通知时

  4、如果垃圾回收算法认为做二代回收是有收效时会触发二代垃圾回收

  5、每一代对象堆的都有一个所占空间大小阀值的属性,当你分配对象到某一代,你增长了内存总量接近了该代的阀值,或者分配对象导致这一代的堆大小超过了堆阀值,会发生一次垃圾回收。因此当你分配小对象或者大对象时,会对应消耗0代堆或者大对象堆的阀值。当垃圾回收器将对象代数提升到1代或者2代时,会消耗1、2代的阀值。在程序运行中这些阀值是动态变化的。

  大对象堆性能影响

  让我们先看下分配大对象的代价。 CLR为每个新对象分配内存时都要保证这些内存清空的,是没有被其他对象使用的(I give out is cleared)。这意味着分配的代价完全被清理(clearing)的代价控制着(除非在分配时触发了一次垃圾回收)。如果清空1byte需要2个周期(cycles),意味着清除一个小的大对象需要170,000个周期。通常情况下人们不会分配超大的对象,比如说在2GHz的机器上分配16M大小的对象,大约需要16ms来清空内存。这代价太大了。

  让我们在看下回收的代价。前面提到过,大对象和2代龄对象一起回收。如果大对象或者2代对象占用空间超过其阀值时,会触发2代对象的回收。如果2代回收因为大对象堆超过阀值被触发,2代对象堆本身没有多少对象可以做回收。如果在2代堆上没有多少对象,这问题不大。但是如果2代堆很大对象很多,过多的2代回收会导致性能问题。如果是临时性的分配大对象,需要很多的时间来运行垃圾回收;也是说如果你持续的使用大对象然后又释放大对象对性能会有很大的负面影响。

  大对象堆上的巨大对象通常是数组(很少有一个对象很大的情况)。如果对象中的元素是强引用,代价会很高;如果元素之间没有相互引用,垃圾回收时不需要遍历整个数组。例如:用一个数组来保存二叉树的节点,一种方法是在节点中强引用左右节点:


1.class Node   
2.{   
3.Data d;   
4.Node left;   
5.Node right;   
6.}   
7.Node[] binaryTree = new Node[num_nodes];