图
接下来让我们从垃圾回收器的角度来看一下内存管理。如果我们想清理一下没用的东西我们可能需要计划一下怎么做才更有效率。很明显,我们需要先区分什么是垃圾,什么不是垃圾。那么我们要先做一个假设:任何东西如果没有用了那么就认为是垃圾。幸好我们身边有两位好朋友:即时编译器(JIT)和统一语言运行时(CLR)。JIT和CLR保持着一个列表关于它们正在使用的对象。我们将使用这个列表作为起始列表。我们将保持关于所有正在使用的对象到一个图表中。所有的活动的对象都将被添加到这个图表里。
这也是垃圾回收器所作的事情,从即时编译器和统一语言运行时那里得到一份关于所有根对象的引用列表。然后递归的查找活动对象的引用去建立一个图表。
根的组成如下:
l 全局/静态指针。一种方法确定我们的对象不会被垃圾回收通过保持他们的引用在一个静态变量里。
l 栈内指针。我们不想抛弃那些我们应用程序需要使用的东西。
l CPU寄存器指针。在托管堆里的任何被CPU寄存器的内存地址指向的对象都应该保留。
在上图当中,对象1和5都被roots直接引用,而对象3则在递归搜索中被发现被1引用。如果我们进行类比,那么对象1是可以看成遥控器,而对象3被看成远端的设备。当所有对象都进入图表中后,我们就进行下一步分析。
调整堆
现在我们已经将我们的要保留的对象加到图表中,现在我们可以分析一下这些东西。
由于对象2是不需要的,所以就像垃圾回收器那样,我们下移对象3并修改对象1的指针。
然后我们在将对象5下移。
现在我们已经将托管堆进行了紧缩调整,为新来的对象腾出空间。
知道垃圾回收器的工作原理就知道移动对象的工作是很繁重的。从这里看出如果我们减少移动对象的大小就能提高垃圾回收器的工作效率,因为减少了拷贝内容。
托管堆之外
有时候垃圾回收器需要执行代码去清理非托管的资源诸如文件,数据库连接,网络连接等等。一种有效的控制这些内容的方式是终结器(finalizer)。
class Sample {
~Sample () {
// FINALIZER: CLEAN UP HERE
}
}
当对象在创建的时候,所有对象附带的终结器(finalizer)都会添加到终结队列里。我们可以说图中的对象1,4,5拥有终结器(finalizer)并都处于终结队列中。让我们看一下当对象2和4在没有被应用程序引用并且垃圾回收器准备好的情况下会发生什么。
图里对象2被作为无用对象处理。但是,当我们处理对象4的时候,垃圾回收器会先查看它的终结队列并重新声明对象4所拥有的内存,对象4被移动并且它的终结器(finalizer)被添加到一个特殊的队列- freachable。
这里有专门的线程去处理freachable队列的成员。一旦对象4的终结器被线程执行,那么它就会从freachable队列中移除。然后对象4就可以被回收了。
而对象4在下一次回收开始前仍然存在。
在创建对象时添加终结器(finalizer)是垃圾回收器的一个额外工作。它要花费很高的代价并且严重影响垃圾回收器和我们的应用程序的性能。所以请确定在绝对必要的情况下再使用终结器(finalizer)。
有更好的方案用作清理非托管资源。就像你想的那样,我们可以使用IDisposable接口取代终结器(finalizer)去关闭数据库链接并清理资源。