欢迎光临散文网 会员登陆 & 注册

C#垃圾回收和托管堆

2023-09-18 23:54 作者:Object404  | 我要投稿

每次使用一种资源,都必须要为这种资源类型分配内存

资源的生命周期

  1. 调用IL指令newobj,为生成的类型分配内存(一般使用c#new 操作符完成)

  2. 初始化内存,设置内存状态并使资源可用,类型的构造器负责设置初始状态

  3. 访问类型的成员来使用资源

  4. 摧毁资源状态以进行清理

  5. 释放内存。垃圾回收独自负责这一步


C#的new操作符会导致CLR执行以下步骤

  1. 计算类型字段所需的字节数,加上对象开销所需的字节数

  2. 检查区域中是否有可以分配这个对象所需的字节数,如果空间足够,就放入对象,当空间不足时,CLR就会执行垃圾回收


垃圾回收算法(CLR使用的是引用跟踪算法)

  1. 引用计数

    • 当一对象被另一个对象引用时或不再被引用时,引用计数就会相应的增加或减少

    • 使用引用计数算法只需要遍历所有引用类型变量,当引用计数为0时就表示没有任何对象引用该对象,可以进行回收

    • 引用计数最大的问题就是无法处理循环引用,比如2个或多个对象互相引用,但这些对象都不再被外部使用,这时这2个对象的引用计数都不会变为0,因此也无法被回收

  2. 引用跟踪

    • 引用跟踪算法只关心引用类型的变量,所有引用类型的变量都被称为根

    • 当进行垃圾回收时,CLR会检查所有根,如果一个根包含null则忽略,并检查下个根

    • 如果不为null,则这个对象就会被标记,并且递归检查这个对象的所有引用,如果这些引用不为空则也会被标记

    • 在标记过程中如果在标记某个对象时发现它已经被标记,那么会使用循环引用检测算法检测这个对象是不是循环引用

    • 检查一遍后,被标记的对象就是可达对象,没有被标记的对象和被定义为循环引用的对象就是不可达对象,不可达对象就可以进行回收


  • 托管堆会被分成3个部分,分别称为0代,1代,2代

  • 当新对象生成时,对象会被分配到0代内存中

  • 如果一个新的对象分配后,造成0代超预算,那么就会触发垃圾回收

  • 在垃圾回收后,0代内存中存活的对象会被转移到1代内存中

  • 在不断的内存迁移中,1代内存也会慢慢扩大,所以当1代内存容量达到预算时,垃圾收集器就会回收0代和1代中的对象

  • 在垃圾回收后,1代内存中存活的对象会被转移到2代内存中

  • 当2代内存也达到预算时,CLR就会触发所有区域的垃圾回收

  • 当3个区域都被占满时,应用就停止响应了


将托管堆分区的好处

  • 垃圾回收时,回收一部分,速度快于回收整个堆

  • 存活时间长的对象大概率是不需要回收的,在每次回收时将幸存的对象向后迁移到后面的内存区域,这样就可以保证,前几个内存区域储存的对象都是新对象,进行垃圾回收时找到垃圾的几率也更高

  • 3个内存区域并不是平均分配的,而是动态调节的

  • 如果在回收0代内存后存活下来的对象很少,这时就会减小0代内存的预算,空间小意味着垃圾回收会更频繁,但每次回收做的事也少了

  • 如果回收0代内存后发现还有很多对象存活,这时就会扩大0带内存的预算,这样垃圾回收的次数将减少,但每次回收时,回收的内存也越多

  • 在其它2个内存区域也会使用类似的算法控制预算


垃圾回收触发条件

  • 创建对象时,发现空间不够

  • 当任何一个内存域的内存超出预算时

  • 代码显式调用GC.Collect方法,这样会触发所有托管堆的垃圾回收,,并且垃圾回收是自动触发的,一般不需要显示调用

  • 系统报告可用内存低时



C#垃圾回收和托管堆的评论 (共 条)

分享到微博请遵守国家法律