.NET的GC垃圾回收机制的工作原理

对学习的知识进行理解,消化,归纳。沉淀出自己的知识体系。第一次写博客,文中如果有错误的地方,希望能够帮忙指出修正,避免误导广大的人民群众,非常感谢!
如需要转载,请注明出处,谢谢!

一、GC是什么

GC,垃圾回收器,用于自动回收托管堆中无效对象的内存,释放内存空间。

1.1 为什么需要自动回收

(1)为什么需要自动回收内存,而不是让开发人员手动管理内存呢(例如C或者C++的开发人员),这是有原因的,接触过这两种开发语言的人估计都知道以下两点原因:

  1. 开发人员忘记释放内存导致内存泄漏。
  2. 试图使用已经释放的内存导致程序错误和安全漏洞。

这两种BUG比其他大多数的BUG都要严重,我们无法预测它们的后果或发生的时间。

1.2 为什么需要释放内存

(2)至于为什么需要释放内存这个就不用解释了吧,计算机的资源是有限的,如果要等到程序关闭的时候再去释放程序占用的内存,那么系统有再多的内存也不够用。程序在运行过程中 ,有很多对象分配内存空间后,使用一次就不再使用了,这不仅占用内存空间,导致内存溢出,还可能影响程序的性能。

1.3 什么是托管堆

(3)什么是托管堆。这个是引用类型对象的内存空间,可以这么说吧,值类型的内存一般在线程栈中,引用类型的内存在托管堆里。线程栈的内存按照对象的先进后出管理,而托管堆的内存是交给GC自动管理,我们一般不知道托管堆中的对象内存会在什么时候被释放。

二、GC的工作原理

2.1 托管堆的资源分配

CLR 要求所有对象都从托管堆分配。进程初始化时,系统分配一个地址空间区域作为托管堆,用一个指针NextObjPtr来指向下一个对象在堆中的分配位置,如图2-1

图 2-1  新初始化的托管堆,其中构造了3个对象
C#创建对象时,会执行以下步骤:

  1. 计算类型所需要的字节数。
  2. 加上每个对象都有的开销的字节数。每个对象都有两个开销字段:类型对象指针和同步块索引
  3. 检查托管堆中是否有足够的空间,如果有,则在指针NextObjPtr指向的地址放入对象,清零所分配的空间,调用类型构造器,new操作符返回对象引用。指针NextObjPtr移到下一个对象分配的地址。

2.2 垃圾回收算法

  1. 当GC进行垃圾回收时,首先会暂停进程的所有线程,防止GC在检查对象的时候被改变对象状态。
  2. GC会检查程序所有的变量,如果它指向的对象(堆)的同步索引标记为1,如果要被指向的对象的字段也有指向另一个对象,则另一个对象也会被标记为1,如果对象已被标记,则停止,避免死循环。
  3. 检查完毕后,托管堆中的对象要么被标记为1,要么没有被标记,没有被标记的对象就可以被GC进行回收。
  4. 托管堆的一些对象被删除后,会造成空间的碎片化问题(即不连续),这时GC会对托管堆进行压缩,其实就是重新对托管堆里面的对象进行移动,使之在地址上连续。如D对象前面的C对象被回收了,那么D对象的地址会向前移动C对象所占用的字节数。
  5. 托管堆中指针NextObjPtr重新指向最后一个幸存对象之后的位置
  6. 恢复应用程序的所有线程,GC工作完成。
    GC回收对象前
    GC回收对象后

2.3 基于代的垃圾回收器的性能提升

CLR 实际的GC是基于代的垃圾回收器,并不是每一次的垃圾回收都会检查托管堆里面所有的对象,它会对程序做了以下的假设:

  1. 对象越新,生存期越短。
  2. 对象越老,生存期越长。
  3. 回收堆的一部分,比回收整个堆快。

事实上我们的程序也正是如此,GC把托管堆分成三部分,分别是第0代、第1代和第2代,程序一开始没有经过GC回收的时候,程序内所有的对象均为第0代。第0代经过一次GC回收而没有被回收的对象升级为第1代,第1代经过一次GC回收而没有被回收的对象升级为第2代,最高代为第二代。

GC会优化只检查第0代的对象,当第1代跟第2代累积达到预算的时候才会检查回收(其他特殊情况例外)。
如图:
在这里插入图片描述
最分配F对象时,发现超出了第0代的预算,则启动GC回收。
在这里插入图片描述 回收第0代对象并进行压缩,调整第0代预算
在这里插入图片描述
达到第0代预算条件,继续进行第0代的回收,第1代对象不检查,加快了检查回收速度
在这里插入图片描述
回收第0代对象并进行压缩,调整第0代预算
在这里插入图片描述
达到第0代预算条件,未达到1代预算,继续进行第0代的回收,第1代对象不检查加快了检查回收速度
在这里插入图片描述
回收第0代对象并进行压缩,动态调整第0代预算
在这里插入图片描述
达到第0代预算条件,也达到1代预算,这次将检查第0代与第1代的所有对象进行回收。
在这里插入图片描述
以上演示了程序开始到产生第2代对象的过程,这只是一个演示的例子,实际上GC的代预算并没有这么小,达到第2代次数要比这个例子要多得多。

关于预算:这里只演示第0代的预算动态调整,其实第1代与第2代也是会进行启发式算法进行动态调整。调整原理:第0代:进行第0代回收时,如果发现没有多少内存被回收,则增加第0代的预算,减少回收频率;如果几乎所有的对象都是垃圾,则减少第0代的预算,增加回收频率,从而提升应用程序的总体性能。垃圾回收器检查有多少内存被回收,有多少幸存对象来动态调整预算。

大对象 :有另外的空间进行分配,并分配就默认为第2代。大对象不进行压缩,代价过高,未来版本可能压缩。
Markdown将文本转换为 HTML。

内存溢出:如果第0代没有回收到足够的内存分配给新对象,那么会执行一次完整的回收,如果还是不够,就抛出OutOfMemoryException异常。

2.4 垃圾回收的触发条件

正常情况下, CLR 检测达到第0代预算触发进行一次GC。但下列情况也会进行:

  1. 代码显示调用System.GC的静态Collect方法
  2. Windows报告低内存情况
  3. CLR正在卸载AppDomain
  4. CLR正在关闭
    不再试图压缩或释放内存,Windows将回收进程的全部内存。

三、其他

GC的一些其他内容,暂时还不知道怎么描述,以后有机会再作补充。

如终结的内部工作原理,其实就是Finalize方法对对象回收的一些影响。
如需要特殊清理的类型例如:System.IO.FileStream,等需要本机资源的类型。
如强制垃圾回收,就是对这个方法的一些讲解,也没有什么特殊的,我觉得就没有必要在这里说了。
如果想详细了解GC的工作原理,可以参考《CLR via C#》一书。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

生命不息-学无止境

你的每一份支持都是我创作的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值