C#托管堆和GC

为什么要自动GC

程序员手动管理内存,看似简单,但可能导致大量的编程错误。忘记释放不再需要的内存而造成内存泄露。试图使用已经释放的内存,然后由于内存被破坏而造成程序错误。而且这种bug往往一般无法预测它们的后果或发生的时间。所以C#提供一个托管堆,自动地处理资源清理。当程序员需要手动清理时,也可以调用类中的Dispose方法。

托管堆和GC是什么

CLR划出一个地址空间区域作为托管堆,同时维护了一个指针NextObjPtr,指向下一个对象在堆中的分配位置。当一个区域被非垃圾对象填满后,CLR会分配更多区域。32位进程最多能分配1.5GB,64位进程最多能分配8TB。

GC就是垃圾收集,当然这里仅就内存而言。Garbage Collector(垃圾收集器),也称为GC)。

怎么进行垃圾回收

(1)引用计数算法

引用计数的做法是,堆上每个对象都维护着一个字段,来统计程序中多少处引用了自己。随着一处到达代码中某个不再需要对象的地方,就递减对象的计数字段。计数字段变成0后,对象就可以从堆里删除了。

引用计数不善于处理循环引用的情况。A持有B的引用,B持有A的引用。A和B的计数都不可能小于1,所以两个对象永远不会被删除。

(2)标记压缩和引用跟踪算法

先简单地把C#的GC算法看作标记压缩算法。阶段1: Mark-Sweep 标记清除阶段,先假设堆中所有对象都可以回收,然后找出不能回收的对象,给这些对象打上标记,最后堆中没有打标记的对象都是可以被回收的;阶段2:Compact 压缩阶段,对象回收之后heap内存空间变得不连续,在heap中移动这些对象,使他们重新从heap基地址开始连续排列,类似于磁盘空间的碎片整理。移动对象位置之后要在对象的指针上加上一个偏移

那具体标记的做法是,从roots出发可以创建reachable objects graph,剩余对象即为unreachable,可以被回收 。可以这样理解roots:堆中对象的引用关系错综复杂(交叉引用、循环引用),形成复杂的graph,roots是CLR在graph之外可以找到的各种入口点。这就是引用跟踪算法。

所以一次GC的主要处理步骤:将线程挂起→确定roots→创建reachable objects graph→对象回收→heap压缩→指针修复。

(3)分代算法

程序可能使用几百M、几G的内存,对这样的内存区域进行GC操作成本很高。分代算法就是将对象按照生命周期分成新的、老的,并对新、老区域采用不同的回收策略和算法。这其实是利用了计算机的时间和空间上的“局部性”原理。

C#的托管堆分为3个代龄区域,相应的GC有3种方式: # Gen 0 collections, # Gen 1 collections, #Gen2 collections。如果Gen 0 heap内存达到阀值,则触发0代GC,0代GC后Gen 0中幸存的对象进入Gen1。如果Gen 1的内存达到阀值,则进行1代GC,1代GC将Gen 0 heap和Gen 1 heap一起进行回收,幸存的对象进入Gen2。2代GC将Gen 0 heap、Gen 1 heap和Gen 2 heap一起回收。

Gen 0和Gen 1比较小,这两个代龄加起来总是保持在16M左右;Gen2的大小由应用程序确定,可能达到几G。粗略的计算0代和1代GC应当能在几毫秒到几十毫秒之间完成。Gen 2 heap比较大时,full GC可能需要花费几秒时间。

注意事项

  1. 目前所说的都是小对象 ,大对象在其他地址空间分配。大对象总是第二代,也不被压缩。
  2. GC只管理内存,非托管资源,如文件句柄,GDI资源,数据库连接等还需要用户去管理。
  3. GC并不是实时性的,这将会造成系统性能上的瓶颈和不确定性。

参考

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值