5.3 垃圾回收
本节将介绍以下内容:
— 垃圾回收机制
— 非托管资源的清理
5.3.1 引言
自动内存管理将开发人员从内存错误的泥潭中解放出来,这一切都归功于垃圾回收(GC,Garbage Collection)机制。
通过对对象创建全过程的讲述,我们理解了CLR执行对象内存分配的基本面貌。一个分配了内存空间和完成初始化的对象实例,就是一个CLR世界中的新生命体,其生命周期大概可以概括为:对象在系统中进行一定的操作和应用,到一定阶段它将不被系统中任何对象引用或操作,则表示该对象不会再被使用。因此,对象符合了可以销毁的条件,而CLR可能不会马上执行销毁操作,而是在适当的时间执行该对象的内存销毁。一旦被执行销毁,对象及其成
员将不可在运行时使用,最后由垃圾收集器释放其内存资源,完成一个对象由生而灭的全过程。
由此可见,在晋书谢安传中自动内存管理是由垃圾回收器来执行的,GC自动完成对托管堆的全权管理,然而一股脑将所有事情交给GC,并非万全保障。基于性能与安全的考虑,很有必要对GC的工作机理、执行过程,以及对非托管资源的清理做一个讨论。
5.3.2 垃圾回收
顾名思义,垃圾回收就是清理内存中的垃圾,因此了解垃圾回收机制就应从以下几个方面着手:
l 什么样的对象被GC认为是垃圾呢?
l 如何回收?
l 何时回收?
l 回收之后,又执行哪些操作?
清楚地回答上述几个问题,也就基本了解的垃圾回收机制。下面本节就逐一揭开这几个问题的答案。
l 什么样的对象被GC认为是垃圾呢?
纸手工制作
oa系统是什么简单地说,一个对象成为“垃圾”就表示该对象不被任何其他对象所引用。因此,GC必须采用一定的算法在托管堆中遍历所有对象,最终形成一个可达对象图,而不可达的对象将成为被释放的垃圾对象等待收集。
l 如何回收?
每个应用程序有一组根(指针),根指向托管堆中的存储位置,由JIT编译器和CLR运行时维护根指针列表,主要包括全局变量、静态变量、局部变量和寄存器指针等。下面以一个简单的示例来说明,GC执行垃圾收集的具体过程。
class A
{
private B objB;
public A(B o)
{
objB = o;
}
~A()
{
Console.WriteLine("Destory A.");
}
}
class B
{
private C objC;
public B(C o)
{
objC = o;
}
~B()
{
Console.WriteLine("Destory B.");
}
}
class C
{
~C()
{
Console.WriteLine("Destory C.");
}
}
public class Test_GCRun
{
public static void Main()
{
A a = new A(new B(new C()));
//强制执行垃圾回收
GC.Collect(0);
GC.WaitForPendingFinalizers();
春节英语手抄报内容
}
}
在上述执行中,当创建类型A的对象a时,在托管堆中将新建类型B的实例(假设表示为objB)和类型C的实例(假设表示为objC),并且这几个对象之间保存着一定的联系。而局部变量a则相当于一个应用程序的根,假设其在托管堆中对应的实例表示为objA泽普县属于哪个市,则当前的引用关系可以表示为图5-6。
图5-6 垃圾收集执行前的托管堆
垃圾收集器正是通过根指针列表来获得托管堆中的对象图,其中定义了应用程序根引用的托管堆中的对象,当垃圾收集器启动时,它假设所有对象都是可回收的垃圾,并开始遍历所有的根,将根引用的对象标记为可达对象添加到可达对象图中,在遍历过程中,如果根引用的对象还引用着其他对象,则该对象也被添加到可达对象图中,依次类推,垃圾收集器通过根列表的递归遍历,将能找到所有可达对象,并形成一个可达对象图。同时那些不可达对象则被认为是可回收对象,垃圾收集器接着运行垃圾收集进程来释放垃圾对象的内存空间。通常,将这种收集算法称为:标记和清除收集算法。
在上例中,a可以看出是应用程序的一个根,它在托管堆中对应的对象objA就是一个可达对象,而对象objA依次关联的objB、objC都是可达对象,被添加到可达对象图中。当Main方法运行结束时,a不再被引用,则其不再是一个根,此时通过GC.Collect强制启动垃圾收集器,a对应的objA,以及相关联的objB和objC将成为不可达对象,我们从执行结果中可以看出类型A、B、C的析构方法被分别调用,由此可以分析垃圾回收执行了对objA、objB、objC实例的内存回收。
l 何时回收?
垃圾收集器周期性的执行内存清理工作,一般在以下情况出现时垃圾收集器将会启动:
(1)内存不足溢出时,更确切地应该说是第0代对象充满时。
(2)调用GC.Collect方法强制执行垃圾回收。
(3)Windows报告内存不足时,CLR将强制执行垃圾回收。
(4)CLR卸载AppDomain时,GC将对所有代龄的对象执行垃圾回收。
冲锋陷阵(5)其他情况,例如物理内存不足,超出短期存活代的内存段门限,运行主机拒绝分配内存等等。
作为开发人员,我们无需实现任何代码来管理应用程序中各个对象的生命周期,CLR职业健康培训内容知道何时去执行垃圾收集工作来满足应用程序的内存需求。当上述情况发生时,GC将着手进行内存清理,当内存释放之前GC会首先检查终止化链表中是否有记录来决定在释放内存之前执行非托管资源的清理工作,然后才执行内存释放。
同时,微软强烈建议不要通过GC.Collect方法来强制执行垃圾收集,因为那会妨碍GC本身的工作方式,通过Collect会使对象代龄不断提升,扰乱应用程序的内存使用。只有在明确知道有大量对象停止引用时,才考虑使用GC.Collect方法来调用收集器。
l 回收之后,又执行哪些操作?
GC在垃圾回收之后,堆上将出现多个被收集对象的“空洞”机器人跳舞,为避免托管堆的内存碎片,会重新分配内存,压缩托管堆,此时GC可以看出是一个紧缩收集器,其具体操作为:GC找到一块较大的连续区域,然后将未被回收的对象转移到这块连续区域,同时还要对这些对象重定位,修改应用程序的根以及发生引用的对象指针,来更新复制后的对象位置。因此,势必影响GC回收的系统性能,而CLR垃圾收集器使用了Generation的概念来提升性能,还有其他一些优化策略,如并发收集、大对象策略等,来减少垃圾收集对性能的影响。例如,上例中执行后的托管堆的内存状况可以表示为图5-7。