垃圾回收平台的工作原理
对资源的访问
- 调用IL指令newobj,为资源的类型分配内存
- 初始化内存,设置资源的初始状态,这一步由构造方法执行
- 访问类型的成员
- 访问结束后摧毁资源状态进行清理
- 由垃圾回收器处理,释放内存
使用IL指令newobj创建对象时,CLR会执行下面步骤:
1. 计算类型的字段所需要的字节数
2. 加上对象的开销(对象指针和同步快索引)所需的字节数,对于32位程序需要加8字节,64位需要加16字节
3. CLR检查保留区能否够提供分配对象所需的字节数,如果够用对象会被放入,如果不够会进行调拨。
垃圾回收器的追踪算法:
根的概念:
- 每个应用程序都包含一组根(root),每个根都是一个存储位置,它包含指向引用类型对象的指针。(根只能是引用类型)
- 静态字段被认为是一个根。
- 任何方法参数或局部变量也被认为是跟。
- cpu寄存器
首先,起始时垃圾回收器(GC)会认为所有对象都是垃圾。
第一阶段:标记(marking)
在此阶段,GC沿着线程栈上行来检查所有的根,如果发现该根引用了一个堆中对象,那么就在对象的“同步块索引字段”上设置一个bit,即进行“标记”操作。
下图展示了一个堆,应用程序直接引用对象A,C,D,F,这些对象被标记,然后在标记D时,垃圾回收器发现该对象有个引用了对象H的字段,因此H对象也被标记。垃圾回收器就是以这种递归的方式遍历所有可达的对象。
第二阶段:压缩(compact)
在这个阶段,GC线性遍历堆,寻找未被标记的“垃圾”对象,如果垃圾对象内存块较小,GC会忽略它;但是如果发现大的、可用的连续内存块,GC会把非垃圾对象移动到这里,用来压缩堆。
终结操作
Finalize方法在垃圾回收结束时被调用,一下5种事件会导致开始垃圾回收:
1. 第0代满,这是目前导致Finalize方法被调用的最常见的一种方式.
2. 代码显示调用System.GC的静态方法Collect.
3. Windows报告内存不足,这是CLR会进行强制垃圾回收.
4. CLR卸载APPDomain,一个AppDomain被卸载时,CLR认为该AppDomain中不存在任何根,因此会对所有代执行垃圾回收.
5. CLR关闭,会调用托管堆中所以的Finalize方法
C#的using语句
在using语句中,我们初始化一个对象,并将它的引用保存到一个变量中,然后在using语句的大括号中访问该变量。编译这段代码时,编译器自动生成一个try finally块,在finally块中编译器会将变量转型成IDisposal,并调用Dispose方法。因此using语句只能用于实现了IDisposable接口的类型。
基于代的垃圾回收
代是CLR垃圾回收器采用的一种机制,目的是提高应用程序的性能。一个基于代的垃圾回收器做了一下三点假设:
- 对象越新,生存期越短
- 对象越老,生存期越长
- 一次回收部分堆比回收整个堆更快
回收过程
堆在初始化时没有对象,最初添加到堆中的对象称为第0代对象。CLR初始化时会为0代空间分配一个预算的大小,假定为264KB。如果分配一个新对象时超过了这个预算大小就会启动一次垃圾回收。
下图所示ABCDE被分配到0代空间,程序运行一段时间后,C,E不可达
当要分配对象F时,由于0代已经没有空间可供分配,这时需要执行垃圾回收,回收CE,与此同时执行压缩,将ABD存放到连续空间。最后存活的对象ABD被提升到1代空间(1代空间分配的大小要大于0代空间)。0代空间为空。
执行垃圾收集后的堆空间如下所示:
每次垃圾回收之后,新的对象总是会被分配到0代空间中。
接下来当我们在分配F到K的对象时,堆的空间如下所示:
现在假定分配新的对象L会造成0代空间满,需要执行GC 。GC在执行垃圾回收前会检查1代已使用的空间大小是否超过配额,没有的话就不对1代执行检查。
所以GC只检查0代空间的对象,发现H和J不可用,可以回收。
经过压缩后,堆空间中的分配如下图所示:
上图中1代空间中的B没有被回收,垃圾收集器只回收了0代空间中的不可用的对象。
接下来我们在分配L到0的对象到0代内存空间中,分配后堆空间如下图所示:
当分配对象P时,假设0代空间已满,执行GC后0代提升为1代,但由于第1代仍未超过配额,所以不对1代处理。
执行GC后堆空间如下所示
可以看到1代的被分配的空间在不断增大,可用配额逐渐减少。我们再次分配对象P到S。
分配后堆空间图如下所示:
当分配对象T时,假设0代空间已满,执行GC后0代要提为1代,假设此时1代对象分配所占空间已达到最大配额,这时GC会检查1代堆空间中的对象,回收不可用对象,并执行压缩,然后1代提为2代。
回收后的堆空间如下图所示:
CLR的托管堆只支持3代:第0代、第1代、第2代。CLR初始化时,会为每一代做预算,0代约为256KB,1代约为2MB,2代约为10MB。
如果垃圾回收器发现在回收0代后存活下来的对象很少,那就有可能降低0代的预算。
垃圾收集器会多次回收低代堆空间中的对象后才执行高代内存空间中的对象回收,这样做大大提高了程序的性能。
参考资料:
CLR via C# (Jeffrey Tichter)