垃圾回收期算法简介

垃圾回收器检查托管堆中是否有应用程序不再使用的对象,如果有,他们使用的内存就可以回收(如果一次垃圾回收之后堆中仍然没有可用的内存,new操作符就会抛出一个OutOfMemoryException)。垃圾回收器如何知道应用程序正在使用一个对象呢?这个说起来比较复杂。

每个应用程序都包含一组根。每个根都是一个存储位置,其中包含指向引用类型对象的一个指针。该指针要么引用托管堆中的对象,要么为null,例如:类型中定义的任何静态字段被认为是一个根。除此之外,任何方法的参数和局部变量也被认为是一个根。只有引用类型的变量才能被认为成根;值类型的变量永远不被认为成根。

垃圾回收期开始执行时,它假设堆中所有对象都是垃圾,换句话说,它假设线程栈中没有引用堆中对象的变量,没有CPU寄存器引用堆中的对象,也没有静态字段引用堆中的对象。垃圾回收期第一阶段就是所谓的Marking(标记)阶段。垃圾回收期沿着线程栈上行检查所有根。如果发现一个根引用一个对象,就在对象的“同步块索引字段”上做一个标记。例如:垃圾回收器发现一个局部变量指向堆中的一个对象。下图展示了一个堆,其中包含几个已经分配的对象。应用程序的根直接引用对象A、C、D、F。

所有这些对象都以标记。标记对象D时垃圾回收期发现这个对象含有一个引用了对象H的字段,造成对象H也被标记。垃圾回收期就是这样,以递归的形式遍历所有可达的对象。

 

技术分享

标记好根和他引用的字段之后,垃圾回收器检查下一个根,并继续标记对象。如果垃圾回收期试图标记一个先前标记过的对象。就会停止沿着这个路径走下去,这个行为有两个目的。首先垃圾回收期不会多遍历一组对象,所以性能能得到提升。其次,如果存在对象的循环链表,可以避免陷入无限循环。

检查好所有根之后,堆中将包含一组已标记的对象和未标记的对象。已标记的对象是通过应用程序可达的对象,而未标记的对象时不可达的,不可达的对象被认为是垃圾,他们占据的内存就可以回收,现在垃圾回收期开始第二个阶段压缩阶段。这个阶段垃圾回收器线性的遍历堆,以寻求未标记对象的连续内存块。如果发现的内存块比较小,垃圾回收期会忽略他们,如果发现的内存块比较大,垃圾回收器就会把非垃圾的对象移动到这里以压缩栈。

 

很自然,移动内存中的对象之后,包含指向这些对象中的指针变量将变的无效。所以垃圾回收期现在开始重现访问应用程序的根,并修改他们来指向新内存的地址。另外,如果对象中的字段指向的是另一个已移动的对象的字段,垃圾回收期也要负责修正这些字段的值。堆内存压缩之后,托管堆得NextObjPtr指针指向紧接在最后一个非垃圾回收之后的位置 ,下图是垃圾回收之后的托管堆。

 

技术分享

 

如你所见,垃圾回收期造成显著的性能损失,这是使用托管堆得主要缺点。但是需要注意的是,垃圾回收期只有在0代已满的时候才回收。在此之前,托管堆得性能远远高于C运行时。最后,CLR的垃圾回收期提供了一些特殊的优化措施,可以大幅提高垃圾回收的性能。(后面讲说代的概念)

作为程序员,我们从前面的讨论中得出两个重要的认识。第一点,不必自己写代码来管理应用程序所用的对象的生存期。第二点,前面一章描素的bug将不复存在。首先,不可能再发生对象泄露的情况。因为任何对象只要应用程序没有根引用它,都会在某个时刻被垃圾回收期回收,所以应用程序不可能发生内存泄露的情况,另外应用程序也不可能再访问已经释放的对象。其次,不可能访问一个已经释放的对象。这是因为如果对象可达,就不会被释放;如果它不可达,应用程序就没办法访问它。另外,由于垃圾回收器导致了内存的压缩,所以托管对象不可能造成托管堆进程虚拟空间地址的碎片化。如果是非托管代码,地址空间的碎片化可能非常严重。但是使用托管堆是这个问题就不发生了,另外是在使用大对象的时候,托管堆仍然有可能碎片化。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。