一 :自动内存管理是.net framework公共语言运行库在托管执行过程过程中提供的服务
自动内存管理是公共语言运行库在托管执行过程过程中提供的服务之一。公共语言运行库的垃圾回收器为应用程序管理内存的分配和释放。
垃圾回收器是用来管理应用程序的内存分配和释放的。在垃圾回收器出现以前,程序员在使用内存时需要向系统申请内存空间。有些语言,例如Visual Basic,可以自动完成向系统申请内存空间的工作。但是在诸如Visual
C++的语言中要求程序员在程序代码中申请内存空间。如果程序员在使用了内存之后忘了释放内存,则会引起内存泄漏。但是有了垃圾回收器,程序员就不必关心内存中对象在离开生存期后是否被释放的问题。当一个应用程序在运行的时候,垃圾回收器设置了一个托管堆。托管堆和C语言中的堆向类似,但是程序员不需要从托管堆中释放对象,并且在托管堆中对象的存放是连续的。
每次当开发人员使用 new 运算符创建对象时,运行库都从托管堆为该对象分配内存。新创建的对象被放在上次创建的对象之后。垃圾回收器保存了一个指针,该指针总是指向托管堆中最后一个对象之后的内存空间。当新的对象被产生时,运行库就知道应该将新的对象放在内存的什么地方。同时开发人员应该将相同类型的对象放在一起。例如当开发人员希望向数据库写入数据的时侯,首先需要创建一个连接对象,然后是Command对象,最后是DataSet对象。如果这些对象放在托管堆相邻的区域内,存取它们就非常快。
二 :垃圾回收原理
当垃圾回收器的指针指向托管堆以外的内存空间时,就需要回收内存中的垃圾了。在这个过程中,垃圾回收器首先假设在托管堆中所有的对象都需要被回收。然后它在托管堆中寻找被根对象引用的对象(根对象就是全局,静态或处于活动中的局部变量以及寄存器指向的对象),找到后将它们加入一个有效对象的列表中,并在已经搜索过的对象中寻找是否有对象被新加入的有效对象引用。直到垃圾回收器检查完所有的对象后,就有一份根对象和根对象直接或间接引用了的对象的列表,而其它没有在表中的对象就被从内存中回收。
当对象被加入到托管堆中时,如果它实现了finalize()方法,垃圾回收器会在它的终结列表(Finalization
List)中加入一个指向该对象的指针。当该对象被回收时,垃圾回收器会检查终结列表,看是否需要调用对象的finalize()方法。如果有的话,垃圾回收器将指向该对象的指针加入一个完成器队列中,该完成器队列保存了那些准备调用finalize()方法的对象。到了这一步对象还不是真正的垃圾对象。因此垃圾回收器还没有把他们从托管堆中回收。 当对象准备被终结时,另一个垃圾回收器线程会调用在完成器队列中每个对象的finalize()方法。当调用完成后,线程将指针从完成器队列中移出,这样垃圾回收器就知道在下一次回收对象时可以清除被终结的对象了。从上面可以看到垃圾回收机制带来的很大一部分额外工作就是调用finalize()方法,因此在实际编程中开发人员应该避免在类中实现finalize()方法。微软建议在实现带有fianlize()方法的类的时侯按照下面的模式定义对象:
对于finalize()方法的另一个问题是开发人员不知道什么时候它将被调用。它不像C++中的析构函数在删除一个对象时被调用。为了解决这个问题,在.Net中提供了一个接口IDisposable。现在我们了解了垃圾回收器工作的基本原理,接下来让我们看一看垃圾回收器内部是如何工作的。目前有很多种类型的垃圾回收器。微软实现了一种生存期垃圾回收器(Generational GarbageCollector)。生存期垃圾回收器将内存分为很多个托管堆,每一个托管堆对应一种生存期等级。
三 :垃圾回收机制实施过程
生存期垃圾回收器遵循着下面的原则:新生成的对象,其生存期越短;而对象生成时间越长的对象,其生存期也就越长。对于垃圾回收器来说,回收一部分对象总是比回收全部对象要快,因此垃圾回收器对于那些生存期短的对象回收的频率要比生存期长的对象的回收频率高。
.Net中的垃圾回收器中目前有三个生存期等级:0,1和2。0、1、2等级对应的托管堆的初始化大小分别是256K,2M和10M。垃圾回收器在发现改变大小能够提高性能的话,会改变托管堆的大小。例如当应用程序初始化了许多小的对象,并且这些对象会被很快回收的话,垃圾回收器就会将0等级的托管堆变为128K,并且提高回收的频率。如果情况相反,垃圾回收器发现在0等级的托管堆中不能回收很多空间时,就会增加托管堆的大小。在应用程序初始化的之前,所有等级的托管堆都是空的。当对象被初始化的时候,他们会按照初始化的先后顺序被放入等级为0的托管堆中。在托管堆中对象的存放是连续的,这样使得托管堆存取对象的速度很快,因为托管对不必对内存进行搜索。垃圾回收器中保存了一个指针指向托管堆中最后一个对象之后的内存空间。
当0等级托管堆被对象填满后,例如候程序初始化了新的对象,使0等级托管堆的大小超过了256K,垃圾回收器会检查托管堆中的所有对象,看是否有对象可以回收。当开始回收操作时,如前面提到的,垃圾回收器会找出根节点和根节点直接或间接引用了的对象,然后将这些对象转移到1等级托管堆中,并将0等级托管堆的指针移到最开始的位置以清除所有的对象。同时垃圾回收器会压缩1等级托管堆以保证所有对象之间没有内存空隙。当1等级托管堆满了之后,会将对象转移到2等级的托管堆。
生存期垃圾回收器的原则也有例外的情况。当对象的大小超过84K时,对象会被放入"大对象区"。大对象区中的对象不会被垃圾回收器回收,也不会被压缩。这样做是为了强制垃圾回收器只能回收小对象以提高程序的性能。
控制垃圾回收器 在.Net框架中提供了很多方法使开发人员能够直接控制垃圾回收器的行为。通过使用GC.Collect()或GC.Collect(int GenerationNumber)开发人员可以强制垃圾回收器对所有等级的托管堆进行回收操作。在大多数的情况下开发人员不需要干涉垃圾回收器的行为,但是有些情况下,例如当程序进行了非常复杂的操作后希望确认内存中的垃圾对象已经被回收,就可以使用上面的方法。另一个方法是GC.WaitForPendingFinalizers(),它可以挂起当前线程,直到处理完成器队列的线程清空该队列为止。
使用垃圾回收器最好的方法就是跟踪程序中定义的对象,在程序不需要它们的时候手动释放它们。例如程序中的一个对象中有一个字符串属性,该属性会占用一定的内存空间。当该属性不再被使用时,开发人员可以在程序中将其设定为null,这样垃圾回收器就可以回收该字符串占用的空间。另外,如果开发人员确定不再使用某个对象时,需要同时确定没有其它对象引用该对象,否则垃圾回收器不会回收该对象。
另外值得一提的是finalize()方法应该在较短的时间内完成,这是因为垃圾回收器给finalize()方法限定了一个时间,如果finalize()方法在规定时间内还没有完成,垃圾回收器会终止运行finalize()方法的线程。
在下面这些情况下程序会调用对象的finalize()方法:
0等级垃圾回收器已满
程序调用了执行垃圾回收的方法
公共语言运行库正在卸载一个应用程序域
公共语言运行库正在被卸载
四:编程可控制的垃圾回收
下面来看一个具体的例子(例子来自msdn),看看垃圾回收的API具体如何使用。
1。简单入门
首先定义如下类:
该类存在一个构造函数和一个析构函数。
然后通过下面的方法进行测试。
运行结果如下:
Demo start: Introduction to Garbage Collection.
Obj(Introduction): BaseObj Constructor
Obj(Introduction): DerivedObj Constructor
Forcing a garbage collection
Waiting for Finalizers to complete
Obj(Introduction): DerivedObj Finalize
Obj(Introduction): Finalize thread's hash code: 3
Obj(Introduction): BaseObj Finalize
Finalizers are complete
Obj(Introduction): BaseObj Constructor
Obj(Introduction): DerivedObj Constructor
Forcing a garbage collection
Waiting for Finalizers to complete
Finalizers are complete
Demo stop: Introduction to Garbage Collection.
通过上面可以看到,通过GC.Collect方法,我们可以强制回收内存。
垃圾回收只是,系统自动调用析构函数,对象生命结束,被占用内存回收。
并且,我们可以通过GC.WaitForPandingFinalzers方法将当前线程挂起,知道垃圾回收处理完毕位置。
而且我们还看到了,对象初始化的时候,先调用的是父类的构造函数,然后才是自己的构造函数。
对象被回收的时候,先调用的是自己的析构函数,然后才是父类的析构函数。
2。复活对象
首先创造下面的类
我们可以看到,在这个类的析构函数中,在allowResurrection为true的情况下,继续给了当前对象一个引用,并且之后调用了GC.ReRegisterForFinalize将自己添加到在垃圾回收器释放对象列表中,并且是自己得以重新启动。后面我们可以来测试他的输出结果是怎样的。
测试代码如下:
测试结果如下:
Demo start: Object Resurrection.
Obj(Resurrection): BaseObj Constructor
Obj(Resurrection): ResurrectObj Constructor
Forcing a garbage collection
Waiting for Finalizers to complete
Obj(Resurrection): ResurrectObj Finalize
Obj(Resurrection): This object is being resurrected
Obj(Resurrection): BaseObj Finalize
Finalizers are complete
Obj(Resurrection): Still alive after Finalize called
Forcing a garbage collection
Waiting for Finalizers to complete
Obj(Resurrection): ResurrectObj Finalize
Obj(Resurrection): This object is NOT being resurrected
Obj(Resurrection): BaseObj Finalize
Finalizers are complete
Demo stop: Object Resurrection.
我们可以看到,在析构函数中,对象重新启用自己之后,仍然可以被使用。
3。IDisposable接口
此接口的主要用途是释放非托管资源。当不再使用托管对象时,垃圾回收器会自动释放分配给该对象的内存。但无法预测进行垃圾回收的时间。另外,垃圾回收器对窗口句柄或打开的文件和流等非托管资源一无所知。
对于该接口,微软定义了一个推荐的使用流程,上面已经提到过。用户可以根据自己的需要,在合适的时候调用Dipose方法来释放非托管的资源。(用到了非托管资源的类,如System.IO.Stream等都实现了IDisposable接口)
我们通过下面的例子来看看他是如何使用的。
测试输出结果如下:
Demo start: Disposing an object versus Finalize.
Obj(Explicitly disposed): BaseObj Constructor
Obj(Explicitly disposed): DisposeObj Constructor
Obj(Explicitly disposed): DisposeObj Dispose()
Obj(Explicitly disposed): DisposeObj Dispose(True)
Forcing a garbage collection
Waiting for Finalizers to complete
Finalizers are complete
Obj(Implicitly disposed): BaseObj Constructor
Obj(Implicitly disposed): DisposeObj Constructor
Forcing a garbage collection
Waiting for Finalizers to complete
Obj(Implicitly disposed): DisposeObj Finalize
Obj(Implicitly disposed): DisposeObj Dispose(False)
Obj(Implicitly disposed): BaseObj Finalize
Finalizers are complete
Demo stop: Disposing an object versus Finalize.
我们可以看到通过调用Dispose()方法,通过GC.SuppressFinalize阻止垃圾回收调用析构函数。
并且在第二次强制回收的时候,由于没有调用Dispose()方法,析构函数得以调用。在析构函数内
由Dispose(false)释放非托管资源,而托管资源则还是由垃圾回收器进行回收。
并且用户可以在自己需要的时候调用实现的Dispose方法来释放非托管的资源。
4。关于ReRegisterForFinalize的测试
关于ReRegisterForFinalize,msdn的描述如下。
ReRegisterForFinalize 方法将 obj 参数添加到在垃圾回收器释放对象前会请求终止的对象列表中。obj 参数必须是此方法的调用方。
调用 ReRegisterForFinalize 方法并不保证垃圾回收器将调用对象的终结器。
默认情况下,实现终结器的所有对象都会被添加到需要终止的对象列表中;但是,对象可能已经完成,或已通过调用SuppressFinalize 方法禁用了终止。终结器可以使用此方法使自己或引用的对象重新起用。
让我们通过例子来了解。
输出结果如下:
Demo start: Suppressing and ReRegistering for Finalize.
Obj(Finalization Queue): BaseObj Constructor
Forcing a garbage collection
Waiting for Finalizers to complete
Obj(Finalization Queue): BaseObj Finalize
Obj(Finalization Queue): BaseObj Finalize
Finalizers are complete
Demo stop: Suppressing and ReRegistering for Finalize.
我们可以看到,通过GC.ReRegisterForFinalize方法,我们可以将对象重复加入到请求终止的对象列表中,
但是通过GC.SuppressFinalize方法,只能禁止垃圾回收器调用第一个对象的析构函数,后面被加入到请求终止类表中的对象的析构函数都将被调用。
5。强制对零代到指定代进行即时垃圾回收
GC.Collect(Int32)可以强制回收指定的代的内存。
6。弱引用(WeakReference )
表示弱引用,即在引用对象的同时仍然允许垃圾回收来回收该对象。
弱引用允许垃圾回收器在回收对象的同时仍然允许应用程序访问该对象。如果需要该对象,则仍然可以获取对该对象的强引用,并阻止对它进行回收。
弱引用特别适合以下对象:占用大量内存,但通过垃圾回收功能回收以后很容易重新创建。
仅在必要时使用长弱引用,因为在终止后对象的状态是不可预知的。
避免对小对象使用弱引用,因为指针本身可能和对象一样大,或者比对象还大。
不应将弱引用作为内存管理问题的自动解决方案,而应开发一个有效的缓存策略来处理应用程序的对象