Microsoft.NET框架程序设计--19 自动内存管理(垃圾收集)

1.垃圾收集平台基本原理解析

访问一个资源所需要的几个步骤:

  1.   调用中间语言(IL)中的newobj指令,为表示某个特定资源的类型实例分配一定的内存空间。当我们在C#或者Visual Basic 以及其他一些编程语言中调用new操作符时,编译器将产生newobj指令
  2. 初始化上一步所得的内存,设置资源的初始化状态,从而使其可以为程序所用。一个类型的实例构造器负责这样的初始化工作。
  3. 通过访问类型成员来使用资源,这根据需要会有一些反复。
  4. 销毁资源状态,执行清理工作
  5. 释放内存,这一步右垃圾回收器全权负责。(注意这里的内存指的是分配在托管堆上的引用类型实例所占有的内存资源。处理托管堆中的内存,系统运行时还有一类内存,即值类型实例所占的内存,他们位于当前线程的堆栈上,垃圾收集器不负责这些内存资源的回收。当值类型实例变量所在的方法执行结束时,他们的内存将随着堆栈空间的消亡自动消失,也就无所谓回收。

内存分配和资源初始化问题:

 通用语言运行时(CLR)要求所有的内存资源(这里仅限于分配给引用类型实例的内存资源)都从一个称为托管堆(managed heap)的地方分配而得。

当应用程序进程完成初始化后,CLR将保留一块连续的地址空间,这段空间最初并不对应任何的物理内存(backing storage)。该地址空间即为托管堆。托管堆上维护者一个指针,我们战且称之为NextObjPtr。该指针表示着下一个新建对象分配时在托管堆中所处的位置。刚开始的时候,NextObjPtr被设为CLR保留地址空间的基地址。

中间语言(IL)指令newobj负责创建新的对象。在代码运行时,newobj指令将导致CLR执行下面几步操作:

  1.   计算类型所有字段(以及其基类型所有的字段,这里所说的字段应该为类型是实例字段)所需的字节总数。
  2. 在前面所得字节总数的基础上再加上对象额外的附加成员所需的字节数。每个对象包括两个附加字段:一个方法表指针和一个SyncBlockIndex。
  3. CLR检查保留区域中的空间是否满足分配新对象所需的字节数--如果需要则提交物理内存。如果满足,对象将被分配在NextObjPtr指针所示的地方。接着,类型的实例构造器被调用(NextObjPtr指针会被传递给this参数),IL指令newobj返回为其分配的内存地址。就在newobj指令返回新对象的地址之前,NextObjPtr指针会越过新对象所处的内存区域,并指示出下一个新建对象在托管堆中的地址。

 当应用程序调用new操作符创建对象时,托管堆中可能没有足够的地址空间来分配该对象。托管堆通过将对象所需要的字节总数添加到NextObjPtr指针表示的地址上来检测这种情况。如果得到的结构超出了托管堆的地址空间范围,那么托管堆将被认为已经充满,这时就需要执行垃圾收集。

2.垃圾收集算法

  垃圾收集器通过检查在托管堆中是否有应用程序不再使用的对象来回收内存。如果托管堆中没有可以用的内存,new操作符将会抛出一个OutOfMemoryException异常。

  每个应用程序都有一组根(root)。一个根是一个存储位置,其中包含一个指向引用类型的内存指针。该指针或者指向一个托管堆重点对象,或者被设为null。另外,一个线程堆栈上所有引用类型的本地变量或者参数变量也被认为是一个根。最后,在一个方法内,指向引用类型对象的CPU寄存器也被认为是一个根。

  当JIT编译器编译一个方法的IL代码时,除了产生本地CPU代码外,JIT编译器还会创建一个内部的表。从逻辑上来讲,该表中的每一个条目都表示着一个方法的本地CPU指令的字节偏移范围,以及该范围中一组包含根的内存地址(或者CPU寄存器)。

  当垃圾收集器开始执行时,它首先假设托管堆中所有的对象都是可收集的垃圾。换句话说,垃圾收集器假设应用程序中没有一个根引用这托管堆中的对象。然后,垃圾收集器遍历所有的根,构造出一个包含所有可以到达对象的图。任何不在该图中的对象都将是应用程序不可访问的对象,因此也是可以被执行垃圾收集的对象。垃圾收集器接着线性地遍历托管堆以寻找包含可收集垃圾对象的连续区块(这些区块现在被认为是自由空间)。一些容量较小的内存块将被垃圾收集器忽略不计。

3.终止化操作

任何封装了非托管资源的类型,例如文件。网络连接等,都必须支持以中国称为终止化的操作。终止化操作允许一种资源在它所占用的内存被回收之前首先执行以下清理工作。要提供终止化操作,我们必须为类型实现一个名为Finalize的方法。当垃圾收集器判断一个对象为可收集的垃圾时,它便会调用该对象的Finalize方法。Finalize方法的实现通常是调用CloseHandle函数(CloseHandle为一个Win32函数,其主要用于关闭一个打开的对象句柄),该函数接受非托管资源的句柄为参数。如果一个封装饿了非托管资源的类型没有定义Finalize方法,那么这些非托管资源将得不到关闭,从而会导致某种程度的资源泄漏。

  1.调用Finalize方法的条件

 有4种事件会导致一个对象的Finalize方法被调用

   (1)第0代对象充满 该事件通常在应用程序代码运行过程中分配新对象的时候发生

 (2)显示调用System.GC的静态方法Collect 我们的代码可以显示地请求CLR执行垃圾收集

  (3)CLR卸载应用程序域 当一个应用程序域卸载时,CLR会被认为该应用程序域中不存在任何根,因此会调用该应用程序域中创建的所有对象上的Finalize方法。

 (4)CLR被关闭 当一个进程正常中断时,他会试图关闭CLR。这时CLR会认为为该进程中不存在任何根,因此会调用托管堆中所有的对象上的Finalize方法。

  2.终止化操作的内部机理

4.Dispose模式:强制对象清理资源

要提供显显式释放或者关闭对象的能力,一个类型通常要实现一种被称为Dispose的模式。Dispose模式定义了开发人员在实现类型的显式资源清理功能时所要遵循的一些约定。如果一个类型实现了Dispose模式,使用该类型的开发人员将能够知道当我们的对象不再被使用时如何显示的释放掉它所占有的资源。

5.弱引用

弱引用允许垃圾收集器收集对象,同时也允许应用程序访问该对象,结果是哪一个要取决于时间。

我们为什么需要弱引用,我们经常用到一些数据结构,他们很容易创建但是却需要大量的内存。

6.对象复苏

7.对象的代龄

代龄是旨在提供垃圾搜集性能的一种机制。一个基于代龄的垃圾收集器(又称季节性垃圾收集器,ephemeral garbage collector)有以下几点假设:

  对象越新,其生存期越短

  对象越老,其生存期越长

  对托管堆的一部分执行垃圾收集要比整个托管堆执行垃圾收集速度更快

(有代龄的垃圾回收工作原理很麻烦,我看了一遍没看多懂)

8.编程控制垃圾收集器

你可能感兴趣的:(Microsoft)