创建、访问、释放资源的几个步骤:

  1. 调用中间语言(IL)中的newobj指令,为表示某个特定资源的类型实例分配一定的内存空间。调用new操作符时,编译器产生newobj指令。
  2. 初始化上一步所得的内存,设置资源的初始状态,使其可以为程序所用。类型实例构造器完成这类工作。
  3. 通过访问类型成员来使用资源。
  4. 销毁资源状态,执行清理工作。
  5. 释放内存。引用类型所占的内存由垃圾收集器全权负责释放。值类型所占的内存将随着堆栈空间的消亡而自动消失,无所谓回收。

  垃圾收集器对内存中的类型表示着何种资源一无所知,所以垃圾收集器并不清楚怎样执行上述步骤中的第4步。为使资源得到正确清理,开发人员必须自己编写这部分工作的代码。这些代码一般被放在Finalize、Dispose、Close方法中。

  大多数类型表示的资源并不需要任何特殊的清理操作。但对于一个表示(或封装)着非托管资源(如文件、数据库连接、套接字等)的类型,在其对象被销毁时,就必须执行一些清理代码。

  通用语言运行时(CLR)要求所有引用类型实例的内存资源都从一个称作托管堆的地方分配而得。

  当应用程序进行完成初始化后,CLR将保留一块连续的地址空间,这段空间最初并不对应任何的物理内存。(在进行的可用地址空间上为其分配内存的行为称作“保留”,进程因此分配而得的内存空间称作“保留地址空间”。由于保留地址空间是一段虚拟地址空间,所以要真正使用它,还必须为其“提交”物理内存。)该地址空间即为托管堆,托管堆上维护着一个指针,称为NextObjPtr。该指针标识着下一个新建对象分配时在托管堆中所处的位置。刚开始时,NextObjPtr被设为CLR保留地址空间的基地址。

  中间语言(IL)指令newobj负责创建新的对象。newobj将导致CLR执行以下几步操作:

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

  由上可看出,对于托管堆来说,分配对象仅意味着在指针上增加一个数组。这比C中操作链表分配内存的做法快许多。并且在托管堆中,连续分配的对象可以保证它们在内存中也是连续的。

  大多数应用程序中,同一时间分配的对象彼此间大多有较强的联系,它们经常在同一时间被访问。应用程序的进程工作集也会变得很多。进程工作集指进程在运行时被频繁地访问的内存布面集合。

  在应用程序调用new 操作符创建对象时,托管堆中可能没有足够的地址空间为来分配对象。如果要求的内存大小超出了托管堆的地址空间范围,那么托管堆将被认为已被充满,需要执行垃圾回收。

posted on 2011-03-30 20:43  辛勤的代码工  阅读(309)  评论(0编辑  收藏  举报