C#托管堆和垃圾回收
文章目录
托管堆基础
分配资源
C#要求所有对象都从托管堆分配。进程初始化时,CLR划出一个地址空间区域作为托管堆。CLR还会维护一个叫NextObjPtr的指针,该指针指向下一个对象在堆中的位置。
一个区域被非垃圾填满后,CLR会分配更多的区域出来,一直重复,直到进程地址空间被填满。(32位进程最多能分配1.5GB;64位进程最多能分配8TB)
C#的new关键字实际上执行了以下步骤:
- 计算类型的字段所需的字节数。
- 加上对象开销所需的字节数(每个对象有两个开销字段:类型对象指针和同步块索引)。
CLR检查区域中是否有足够分配对象所需的字节数。如果有足够的空间,就在NextObjPtr处放入该对象,并且将NextObjPtr返回给类型的构造器(this)。NextObjPtr会在自身的基础上加上对象占用的字节数来得到一个新值,即下一个对象的地址。

垃圾回收算法
CLR的垃圾回收机制采用的是引用跟踪算法,该算法只关心引用类型的变量,因为只有这种变量才能引用堆上的对象。我们将所有这些引用类型的变量称为根。
垃圾回收的机制有以下步骤:
- 垃圾回收开始时,
CLR首先暂停所有线程(防止检查期间对象状态改变)。 - 进入标记阶段,
CLR遍历堆上的所有对象,并将同步块索引中的某一位设置为0(标记为0时表示对象需要被删除)。 CLR检查所有根,如果这个根引用了堆上的对象,那么CLR会标记这个对象,将同步块索引的值修改为1。如果一个对象被标记,CLR还会检查那个对象中的根,标记他们引用的对象。 如下图,B对象含有一个E对象的引用,标记B对象的同时也会标记E对象。

- 检查完成后,堆中的对象分为已标记和未标记两种状态。
- 已标记的对象不能被垃圾回收,因为有根的引用,我们成为可达。
- 未标记的对象将被回收,因为已经没有根的引用,我们成为不可达。
CLR进入(压缩)阶段,首先将可达的部分连续排列。在压缩过程中,因为会移动对象在内存中的位置,所以CLR还需要重新为每个对象计算新的指针位置。压缩完成后,NextObjPtr将指向最后一个可达对象的位置。

这部分只是简单介绍下托管堆的初始化和垃圾回收的基本算法。 后面会继续开文章或者补充具体每一代是如何进行垃圾回收的。