你的位置:首页 > ASP.net教程

[ASP.net教程]理解垃圾回收平台的基本工作原理


理解垃圾回收平台的基本工作原理

每个程序都有使用不同的资源,比如文件、内存缓冲、屏幕空间、网络连接、数据库连接等,其实在面上对象的程序设计中,每个类型都代表可供程序使用的一种资源,

要使用这些资源就必须为这些资源类型分配内存。下面是访问一种资源所需的步骤:

(1)  调用IL指令的newobj,为代表资源的类型分配内存,在c#中使用new操作符,编译器就会自动生成该指令。

(2)  初始化资源,设置资源的初始状态,使资源可用。类型的实例构造器负责这个工作。

(3)  访问类型成员来使用资源。

(4)  摧毁资源状态以进行清理。

(5)  释放内存。垃圾回收器负责这一步。

看是如此简单的过程,有多少程序员忘记释放不再需要的内存?又有多少程序员试图调用已释放的内存?

进行非托管编程时,应用程序的这两种bug比其它bug严重很多,一般都无法预测到他们的后果,如果是其它bug,可以发现程序行为异常来修正。但是这两种bug会造成资源泄露和对象损坏,是应用程序表现不可预测。

通常进行资源管理很难而且很枯燥。它会极大分散开发人员的注意力,使之无法专注要真正解决的问题。要是有一种机制能为开发人员简化这个工作,那是一件不错的事情,幸好有这中机制,这就是垃圾回收(简称GC –garbage collection).

有了GC使的开发人员的到解放,就不必跟踪内存的使用,也不必知道什么时候释放内存。但是垃圾回收对内存中的类型资源一无所知。这意味着GC不知道怎么执行前面的第四步:摧毁资源状态以进行清理 。为了使资源得到正确的清理,开发人员必须编写知道如何正确清理资源的代码。这些代码通常放在Finalize,Dispose和close方法中,但是GC提供了一些帮助,使得开发人员大多数时候可以跳过第四步。

 

另外要注意,值类型、集合类型、String、Attribute、Delegate和Exception所代表的资源无需进行特殊清理。

如果一个类型代表非托管和本地资源(比如文件、数据库连接、套接字、mutex、位图和图标等),那么在对象准备回收时,必须进行一些清理代码。

2、从托管堆中分配资源

CLR要求所有托管代码的资源在托管堆中分配。这个堆和C运行时的堆非常相似,只是你永远不要从托管堆中删除对象(应用程序不需要的对象会自动删除),这就引出一个问题:托管堆如何知道应用程序不需要一个对象?

目前有好多种垃圾回收器的算法,不同算法都针对不同环境进行优化,已提供这种环境下最佳的性能。本系列将关注Microsoft.NET Framework的CLR所采用的垃圾回收算法。

前面说到new负责创建一个对象,导致编译器生成一个newobj指令。该指令会导致CLR执行以下步骤。

(1)  计算类型(以及所有基类型)资源所需要的字节数。

(2)  加上对象开销所需的字节数。每个对象都有两个字节的开销:一个是类型对象指针和一个同步块索引(可能不同资料上面名字翻译不一样,以后章节中再对这两种不同的开销做讲解,你也可以去MSDN网站上查一些资料),真对32位应用程序和64位应用程序分配的字节是不一样的。

(3)  CLR检查保留区域是否能提供分配对象所需的字节数,如果托管堆有足够的空间对象会被放进去,这里有个NextObjPtr指针,该对象是在这个指针指向的地址放入的。并且为他分配的字节数就会清零,接着调用类型实例构造器,IL指令newobj会返回对象的地址。在返回地址之前,NextObjPtr指针的值会加上对象占据的字节数,这样会得到一个新值,他指向下一个对象放入托管堆得地址。

                           

                

为了对比,让我们看一下C运行时堆如何分配。在C运行时堆中,为对象分配内存要遍历一个数据结构组成的链表。一旦发现有足够大的块,那个块就会被拆分,同时修改链表节点的指针,以确保链表的完整性。对于托管堆,分配对象只需要在指针上加一个值,这个显然明显快得多。事实上,托管堆分配对象的速度几乎可以与从线程栈分配内存媲美。另外,大多数堆(包含C运行时堆)都是在他们找到可用空间的地方分配,所以连续创建几个对象,有可能被分散。中间相隔数MB的地址空间。但是在托管堆中连续分配对象能够确保他们对象的地址是连续的。

综合上诉,似乎托管堆得实现的简单行和速度方面远远优于C运行时堆。但是这有个特别注意的细节你的搞清楚,托管堆之所以有这么多好处,是因为它做了一个相当大胆的假设:“地址空间和存储是无限的”。这个假设简直荒谬之极。所以托管堆必须通过某种机制来允许它这么假设。这种机制就是垃圾回收。

应用程序调用new创建对象时,可能没有足够的地址空间来分配该对象,托管堆将需要的字节加到NextObjPtr指针中的地址上来检查这种情况。如果结果值超过了地址的末尾,表明托管堆已满,必须进行垃圾回收了。

 备注:本系列文章来自<<CLR via C#>>第三版