1、摘 要基于.NET 平台的开发语言中,最让开发人员爽的一点就是垃圾回收处理机制,在编码过程中,终于可以解放你的双手来关注更重要的事情。很多的资料中在讲到.NET 中的垃圾回收机制时都说“CLR 在合适的时候进行垃圾回收 ”,但什么时候才是“合适的时候”?内存又是如何分配的?CLR 是如何对内存进行回收的?这一章我们来讨论有关垃圾回收的相关内容。第一节 垃圾回收机制早期的 C/C+开发中,一个对象的生命周期大概像这样:计算对象大小查找可用内存初始化对象使用对象摧毁对象。如果在上面的过程中,开发人员忘记了“摧毁对象”这一步骤,则很有可能导致内存泄露!这是一个非常可怕的事情!幸好,CLR 的开发人员
2、为我们解决了这一问题,在.NET Framework 中引入了垃圾回收机制,使得开发人员不需要再过多地关注内存释放的问题,CLR 会在合适的时候进行执行垃圾回收来释放不再使用的内存。这里就像一个邪恶的男人所说的话:给我一个女人,我能创造一个民族!其实一个新世界你都可以去创造,前提是要有一个足够大的星球内存来容纳你的子孙!CLR 就是这么认为的。在激活一个进程时,CLR 会先保留一块连续的内存,在主线程启动过程中,可能会初始化一系列对象,CLR 先计算对象大小及其开销所占用的字节数,接着会在连续的内存块中为这些对象分配内存,这些对象被配置在第 0 代内存,在构造第 0 代内存的时候会分配一个默认
3、大小的内存,随着程序的运行,可能会初始化更多的对象,CLR 发现第 0 代内存不能装载更多的新生对象,此时 CLR 会启动垃圾回收器对第 0 代内存进行回收,不再使用的对象所占用的内存会被释放,接着把 0 代对象提升为第 1 代,然后把新生对象配置在第 0代内存区中。http:/ CLR 使用了 3 个阶段的代,每次新分配的对象都会被配置在第 0 代内存中,最老的对象在第 2 代内存中,每次为新对象分配内存时,都可能会进行垃圾回收以释放内存,很显然 CLR 认为“内存永远也使用不完 ”,很显然CLR 为我们自动管理了内存垃圾,很显然 CLR 的这个“认为”在我们开发人员看来是不成立的,我们从以
4、下几个方面来解读垃圾回收机制。第二节 内存分配垃圾回收是对引用类型而言的。CLR 要求引用类型的对象从托管堆中分配内存的,值类型是从栈中分配内存。在 C#中通常使用 new 操作符来创建一个对象,编译器将会在 IL 中生成 newobj 指令,执行一个newobj 指令会有以下过程:(在前一节中我们已经知道,在一个进程启动时会先保留一个连续的内存块)先计算类型及其基类型的字段所需要的字节数 A,再计算类型对象的指针和一个同步索引块共 8 或 16 个字节,到此总共需要(A+8 或 18)字节的内存,CLR会检查当前进程区是否有足够的内存来容纳(A+8 或 16)个字节的对象,如果有,则将新对象
5、放其中,否则 CLR 进行垃圾回收,释放不再使用的内存来容纳新的对象,在整个进程的生命周期中,CLR 会维护一个指针 P,它一直指向当前进程所分配的最后一个对象内存的结尾处而不会跑出当前进程内存区边界每次计算新的将要创建的对象所需要的字节数时,CLR 都是通过 P 加上新的需要的字节数进行检查可用内存区,如果超出了地址末尾,则表示当前的托管堆已经被用完,准备进行垃圾回收了。由于进程拥有一个独立连续的内存区,所以 CLR 能保证创建的新对象基本上都是紧挨着放置的。第三节 代当托管堆的内存被用完,新生的对象无处放置时,CLR 就要开始进行垃圾回收了,随着程序的持续运行,托管堆可能越来越大,如果要对
6、整个托管堆进行垃圾回收(下面会讲到如何回收),势必会严重影响性能,因为有时可能仅仅需要数十个字节就能容纳新的对象,有时候可能要对可达的对象进行搬迁,为了小范围有目的性地进行垃圾回收,CLR 使用了“代”概念来优化垃圾回收器,代是垃圾回收机制使用的一个逻辑技术,也是一种算法,它把托管堆中的内存分为 3 个代(截止到目前.NET Framework4.0 有 3 个代:0、1 、2)。进程在初始化时,CLR 为托管堆初始化为包含 0 个对象的一块内存区域,新添加到堆中的对象为第 0 代对象,CLR 在初始化第 0 代内存区时会分配一个默认的配额,假设为512K,不同的.NET 框架和版本,可能这个
7、配额不相同。假设进程及其线程初始化完成后分配了 4 个对象这 4 个对象占据了 512K 的内存,程序继续运行,当再分配第 5 个对象 Obj5 的时候,发现第 0 代已无可用内存,此时 CLR 会启动垃圾回收器进行垃圾回收,假如上面的 Obj3 已经无效,此是 Obj3 的内存会被释放出来,接着搬迁 Obj4 对象到 Obj3 的位置(在 Obj2的内存地址末尾处),存活下来的对象 Obj1、Obj2 和 Obj4 会被提升为第 1 代对象,第1 代的内存区域根据程序运行的情况,CLR 可能会为其分配 20M(也可能是其他值)大小的内存区,第 0 代内存暂时为空,接着将 Obj5 分配到第
8、0 代内存区 程序继续运行,并又新分配了 4 个对象 Obj6-Obj9,且此时 Obj2 和 Obj5 都不再使用,即为不可达对象,此时需要再创建一个新对象 Obj10,但发现第 0 代的 512K 内存已经用完,所以 CLR 再一次启动垃圾回收器进行垃圾回收,这一次垃圾回收器会认为第 0 代的新对象生命周期短,http:/ 所以先对第 0 代进行回收,并将存活对象提升到第 1 代中,垃圾回收器发现此时第 1 代中的对象远远小于 20M,所以放弃对第 1 代的回收,程序继续运行,分配 N 多的新对象,当把第 0 代的对象提升到第 1 代,而第 1 代对象超 20M 时,则会对第 1 代的对象
9、进行回收,第 1 代存活的对象被提升为第2 代,第 0 代存活的对象被提升为第 1 代 每一次垃圾回收的过程,垃圾回收器会根据实际使用情况自动调整第 0、1 、2 代的默认配额大小,比如可能将第 2 代调整为 200M,几分钟过后可能将其调整为 120M,也有可能是 1024M,程序继续运行,当对 3 个全部进行了垃圾回收且重新调整配额后,可用内存还不足以放置新对象,CLR 就会抛出 OutOfMemoryException 异常,此时活神仙也无法施救了。原来 CLR 认为“内存永远也使用不完 ”也是有条件的啊!第四节 垃圾回收过程托管堆中的一个对象,当线程中有变量对其引用则为可达对象,否则为
10、不可达对象。在一次垃圾回收过程开始时,垃圾回收器会认为堆中的所有对象都是垃圾。第一步是标记对象,垃圾回收器沿着线程栈上行检查所有根,静态字段、方法参数、活动中的局部变量以及寄存器指向的对象等都是根,当发现有根引用了托管堆中的对象 A 时,垃圾回收器会对此对象 A 进行标记,在标记 A 时,如果检测到对象 A 内又引用了另一个对象 B,则也对 B 进行标记,对一个根检测完毕后会接着检测下一个根,执行同样的标记过程,代码中很有可能多个对象中引用了同一个对象 C,垃圾回收器只要检测到对象 C 已经被标记过,则不再对对象 C 内所引用的对象进行检测,以防止无限循环标记。有标记的对象就是可达对象,未标记
11、的对象就是不可达对象。第二步是搬迁对象压缩堆,垃圾回收器遍历堆中的所有对象来寻找未标记的对象,因为未标记的对象是垃圾对象,可以进行回收,如果发现对象较小,则忽略,否则会先释放这些垃圾对象所占的内存,再把可达对象搬迁到这里以压缩堆,在搬迁可达对象之后,所有指向这些对象的变量将无效,接着垃圾回收器要重新遍历应用程序的所有根来修改它们的引用。在这个过程中如果各个线程正在执行,很可能导致变量引用到无效的对象地址,所以整个进程的正在执行托管代码的线程是被挂起的。其实在垃圾回收器准备开始一次回收时,正在执行托管代码的所有线程都必须被挂起,挂起时,CLR 会记录每个线程的指令指针以确定线程当前执行到哪里以便
12、将来在垃圾回收结束后进行恢复。如果一个线程的指令指针恰好到达了一个安全点,则可以挂起该线程,否则 CLR 会尝试劫持该线程,如果还未到达安全点,则等待几百毫秒后 CLR 会尝试再一次劫持该线程,有可能经过多次尝试,最终挂起该线程,当当前进程的所有执行托管代码的线程都挂起后,垃圾回收器就可以开始工作了。(有关线程劫持可查找相关资料)。垃圾回收器回收完毕后,CLR 恢复所有线程,程序继续运行。可见,垃圾回收对性能影响之巨大!第五节 大对象在创建新对象时,任何大于等于 85000 字节的对象都被认为是大对象,这些对象的内存是从大对象堆中分配的,大对象总是被认为是第 2 代对象,要尽量避免分配大对象来
13、减少性能损伤,为了提高性能,垃圾回收器不对大对象进行搬迁压缩,只在回收第 2 代内存时进行回收。第六节 手工进行回收一般的情况下,CLR 会智能地在必要的时候更行垃圾回收,但我们也可以在我们愿意的情况下手动启动垃圾回收器,System.GC 类提供了重载版本的静态方法来启动垃圾回收器:/对所有代进行垃圾回收。GC.Collect();/对指定的代进行垃圾回收。GC.Collect(int generation); /强制在 System.GCCollectionMode 值所指定的时间对零代到指定代进行垃圾回收。GC.Collect(int generation, GCCollectionMode mode); 在上一节中我们已经知道,每一次垃圾回收过程都会导致性能损伤,所以我们尽量避免调用这 3 个方法进行垃圾回收,当然必要的时候也可以调用。不仅仅以上谈到几种情况下会启动垃圾回收器,当 CLR 接到 Windwos 发出内存告急通知时也会启动垃圾回收、CLR 卸载 AppDomain 时也会启动垃圾回收。