.NET中的垃圾回收(下)

类别:.NET开发 点击:0 评论:0 推荐:

垃圾回收性能的优化

l         弱引用(WeakReference

l         代(Generations

 

弱引用(WeakReference

       弱引用(WeakReference)是提高性能的一种方式,用于减少托管堆中大对象的压力。

      

       当一个根指向一个对象时,它被称为这个对象的一个强引用并且这个对象不能被回收,因为应用程序能遍历到这个对象。

 

       当一个对象有一个指向它的弱引用(WeakReference)时,基本上是指如果有内存请求并且GC启动时,这个对象可以被回收,当应用程序再次尝试去访问这个对象时,访问将会失败。另一方面,为了能访问一个被弱引用(WeakReference)的对象,应用程序必须获得一个对这个对象的强引用。如果应用程序在垃圾回收器回收这个对象之前获得了它的强引用,GC将不能回收这个对象,因为有这个对象的强引用存在。

 

       托管堆包含两个管理弱引用(WeakReference)的内部数据结构:短弱引用表和长弱引用表。

 

       两种类型的弱引用:

 

l         短弱引用不追踪复苏。

也就是说,一个有短弱引用的对象会被立即收回,而不用等到运行Finalize方法。

l         长弱引用追踪复苏。

也就是说,只有当长弱引用表中的对象的存储空间可收回的时候GC才回收这个对象。如果对象有Finalize方法,是在Finalize方法被调用了之后并且对象不能复活了。

 

       这两个表简单的存放着分配在托管堆中对象的指针。最初,两个表均为空。当你创建一个弱引用(WeakReference)对象时,对象不从托管堆中分配。而是在一个弱引用表中分配一个空的存储位置;短弱引用使用短弱引用表,长弱引用使用长弱引用表。

 

       让我们看一个例子,看看GC运行时会发生些什么。下面的图(图1和图2)显示了GC运行前和运行后所有内部数据结构的状态。

 

1GC运行前

 

2GC运行后

 

以下是GC运行时进行的操作:

 

1.  GC为所有可遍历的对象建一张图。在上面的例子中,图包含对象B,C,E,G

2.  GC扫描短弱引用表。如果表中指针指向的对象不在图中,那么这个指针标识的是一个不可遍历的对象,短弱引用表中的这个位置置为null。在上面的例子中,对象D的位置置为null,因为它不是图的一部分。

3.  GC扫描Finalization队列。如果队列中的指针所指的对象不在图中,那么这个指针标识一个不可遍历的对象,指针从Finalization队列中移到FReachable队列中。这时,对象被认为是可遍历的,所以加到图中。在上面的例子中,对象A,D,F是不包含在图中但看作是可遍历的对象,因为它们属于Finalization队列。进而Finalization队列被清空。

4.  GC扫描长弱引用表。如果表中的指针指的对象不在图中(现在图包括FReachable队列中指针所指的对象),那么指针标识一个不可遍历的对象,所在位置置为null。由于对象CF都包含在图中,都不置null

5.  GC整理(Compact)内存,挤出不可遍历对象留下的空隙。在上面的例子中,对象H是唯一从堆中删除的对象,它所分配的内存被收回。

 

代(Generations

       由于垃圾回收要在停止整个程序的情况下才能完成,它们可能会在程序执行期间进行任意长时间的中断。GC也有可能中断为满足实时系统的需求而要求及时响应的事件。

 

       GC中有一个特征叫代(Generations),就是专门为提高性能而设计的。一个多代的GC是通过对观察用各种语言编写的大部分程序而得到两个事实进行仔细分析而得到的:

 

1.  新创建的对象拥有更短的生命周期。

2.  越老的对象,存活的越久。

 

多代的回收器通过对象的年龄把它分成若干组,并且年轻的对象比年老的对象回收的更频繁。初始化时,托管堆不含任何对象。所有新的对象都被添加到第0代堆中,直到堆装满了并触发垃圾回收。由于大部分对象存活的时间很短,只有一小部分年轻的对象在第一次回收时存活下来。一旦一个对象在第一次回收后存活下来后,它就被提升到第1代。在垃圾回收后可以说新的对象都在第0代堆中。只有当第0代的堆装满时垃圾回收才会再次被触发。所有第0代存活下来的对象被整理提升到第1代中。然后第0代不含任何对象了,但是所有新的对象都进入了第0代。

 

因此,作为当前代中“成熟”(存活于多代回收器)的对象,它们都会被移到下一级更老的代中。第2代是CLRGC所支持的最大的代。以后回收时,第2代存活的对象将只是简单的停留在第2代。

 

因此,把堆划分成对象的代并且回收和整理更年轻的代中的对象提高了垃圾回收算法的效率,因为从堆中收回了大量的有意义的空间,同时比起回收器检查所有代中的所有对象要快得多。

 

一个能执行多代回收的GC,每次回收都要确保(至少要尽可能)所需时间小于某个最大时间,以帮助为实时环境能做一些配套的实时操作,同时也防止出现让用户明显感觉到的中断现象。

 

垃圾回收相关神话

GC显然比手工内存管理要慢

应解释:不是一定的。现代垃圾回收器看起来运行时和手工存储分配(malloc/freenew/delete)一样快。在一些特殊的程序中,垃圾回收运行的可能不如为用户专门设计的自定义内存分配那么快。但从另一方面说,为了使手工内存管理正确的工作而添加的额外代吗(比如说,显示的引用计数)常常比GC所做的要昂贵的多。

 

GC会使程序中断

对应解释:由于垃圾回收器在查找和回收垃圾对象时通常停止整个应用程序,他们可能会导致中断时间过长而让用户觉察到。但是通过现在优化计数,这些可以感觉到的中断完全可以避免。

 

手工内存管理不会导致中断

对应解释:手工内存管理并不能确保性能。它可能由于大量的分配或释放内存工作而导致中断。

 

使用GC的程序很大并且臃肿;GC不适合小的程序或系统

对应解释:尽管在复杂的系统中使用GC很有优势,也没有理由认为GC在其它尺寸的程序中会引入什么大的开销。

 

我已经听说了GC会使用两次大量的内存

对应解释:对于原来的GC这可能是个事实,但并不是垃圾回收器都是这样的。用于GC的数据结构比那些手工内存管理的要大的多。

 

 

本文地址:http://com.8s8s.com/it/it45188.htm