利用对象代龄提高垃圾收集效率
代龄是旨在提高垃圾收集器性能的一种机制。一个基于代龄的垃圾收集器有以下几点假设:
CLR将对象代龄分为0、1、2,共三代,且对每一代的对象所占用内存容量都设定了一个阀值,代龄越大,内存容量阀值也越大。代龄对象所占用内存超出阀值时,将引发垃圾收集。
在托管堆初始化时,其中不包括任何对象。这时添加到托管堆中的对象被称为第0代对象。当第0代对象所占用内存超过了阀值时,垃圾收集器就必须启动了。经过垃圾收集后存活下来的对象被认为是第1代对象,同时第0代对象暂时空缺。之后又有新对象分配而成为第0代对象。
经过一段时间,如果第0代对象所占用内存超过阀值,垃圾收集器又被启动,此时垃圾收集器会同时检查第1代对象所占内存是否超出阀值,如果超出,则垃圾收集器不光对第0代对象进行垃圾收集,还会对第1代对象进行垃圾收集;如未超出,则仅对第0代对象进行垃圾收集。此次收集过后,第0代对象的代龄提升为第1代,第1代对象若被执行了垃圾收集,则代龄提升为第2代。
由此,每次因新分配对象导致第0代对象超出阀值时都会执行垃圾收集。但每次垃圾收集仅对占用内存超出阀值的代龄对象进行收集。这样保证越老的对象执行垃圾收集的机会越少,且经常仅在托管堆中一部分(即超出阀值的代龄对象上)执行垃圾收集,减小了每次垃圾收集的工作量,提升了垃圾收集的效率与性能。
CLR的垃圾收集器还会从每次执行垃圾收集的结果中学习应用程序行为,自动调整各代龄的内存容量阀值,从而提高程序性能。
垃圾收集器在APS.Net Web窗体和XML Web服务应用程序中的工作表现相当出色。对ASP.Net应用程序来说,当客户请求到达时,会有许多新的对象被构造,这些对象会根据客户的行为执行某些操作,然后结果返回客户。之后,所有用来响应客户请求的对象都将成为垃圾对象。这样,每一次垃圾收集都能回收许多内存。
编程控制垃圾收集器
System.GC类型为我们的应用程序提供了直接控制垃圾收集器的一些方法。
GC.MaxGeneration属性:返回托管堆支持的最大代龄,目前返回值为2。
GC.GetGeneration(object obj)方法:返回int类型,获取一个对象的代龄。
GC.GetGeneration(WeakReference)方法:返回int类型,获取一个弱引用对象的代龄。
GC.Collect()方法:强制对所有代龄对象执行垃圾收集。大多数情况下,我们应该避免手动调用Collect方法,而应该让垃圾收集器根据自己的判断来执行。
GC.Collect(int Generation)方法:强制对指定代龄的对象执行垃圾收集。
GC.WaitForPendingFinalizers()方法:挂起执行线程,直到处理终止化可达队列的线程清空了该队列,并完成每个对象的Finalize方法调用为止。
如下例代码:
GC.Collect(); //强制执行垃圾收集
GC.WaitForPendingFinalizers(); //等待第一次收集中发现的终止化对象的Finalize方法完成
GC.Collect(); //将第一次收集中发现的终止化对象内存回收
大尺寸对象
任何占用内存等于或超过85000字节的对象都被认为是大尺寸对象。大尺寸对象从一个特殊的大尺寸对象托管堆中分配。该托管堆中对象的终止化和内存释放行为与普通对象相同。但大尺寸对象不会被压缩,因为在托管堆中搬移85000字节以上的内存块会浪费CPU比较多的时间。
另外,大尺寸对象总被认为是第2代对象,因此我们只应该为那些需要保存很长时间的资源创建大尺寸对象。分配生存期比较短的大尺寸对象将导致垃圾收集器频繁地收集第2代对象,这无疑会操作应用程序的性能。