CLR 垃圾回收算法

c#相较于c,c++而言,在内存管理上为程序员提供了极大的方便,解放了程序员与内存地址打交道,提高了程序员的工作效率。比如c中分配的malloc堆空间没有释放导致的内存泄露,数组越界导致的踩内存错误,使用了已释放的内存空间错误等等。这些在C#中统统的都不存在,主要是由于clr提供的安全检查机制以及垃圾回收机制。本篇文章主要来介绍常用的垃圾回收算法以及CLR中使用的垃圾回收算法。

在通常的情况下当分配对象时发现内存堆空间不足时,此时GC会执行垃圾回收算法。默认情况下,进程启动,会被分配相应的堆空间的大小,堆空间的大小受到进程虚拟空间的限制。32位系统上的堆空间最大为1.5G,64位为8T,

1.引用计数:

  引用计数的回收算法的思想是:对于堆中创建的一个对象,内部维护一个引用计数,但该对象被引用的时候,该对象的引用计数加1,当该对象的引用超过了生存期时(如方法结束)或者指向新的对象时,该对象的引用计数减一,当对象的引用计数为0时则可以被垃圾收集。该算法的优点是实现简单,垃圾收集不会终止线程的执行,适用于实时的环境,但是缺点时不能够解决循环引用。例如,一个对象A指向B,而B又指向A,但是没有其他的引用指向两者,这时永远也不能回收两者。

2.引用追踪算法:

  引用追踪算法的基本步骤是: 当GC启动时,1.首先暂停所有的线程。2.GC标记阶段:首先遍历堆中所有的对象,将其中的标识置0(标识包含在同步块索引字段中),表明初始时都是可以被删除的。其次CLR查找所有的根引用(这里的根引用指的是一些引用变量,包括类静态引用变量,实例引用变量,方法局部引用变量以及方法参数),如果根引用为空的话,CLR会忽略该引用,并且查找下一个根引用,当该根引用不为空时,将其指向的对象的标识置1,同时查找该对象里的根引用,以此进行标识下去,当遇到对已经标识过的对象时,他将不会再查找该对象的里的根引用,防止循环引用导致的无限循环。

下面举个例子,如图1所示,堆中的对象有ABCDEFGHIJ,当CLR执行GC时,在标记的第二阶段他会查找根引用,发现其指向ACD,将该对象的对应的标识位值1,同时发现D也引用了H,则将H的标识位置1,当标记阶段执行完成,GC开始执行对象的压缩,将存活下来的对象压缩到连续的空间中,释放掉不再使用的对象,GC执行压缩后的对象内存分配如图2所示。执行压缩的好处是:对象都在连续的内存空间中,减少了应用的工作空间,提高了访问对象的性能。其次,防止内存碎片的产生。最后,当存活的对象压缩到连续的空间中时,他们的内存地址发生了相应的改变,这是我们需要改变根引用指向的对象移动后的地址。图中的NextObjPtr指向的是接下来将分配对象的地址的位置。当压缩完成后,CLR恢复所有的线程,以使其继续执行。

         图1,未执行GC前堆中对象的分布。

        图2.执行GC后,堆中的对象分布

3.基于代的垃圾回收算法。

基于代的垃圾回收算法是根据三个方面的推断所提出来的:1.越新的对象,越短的生命周期。2.老对象将会有更长的生命周期。3.一次回收部分堆空间比回收整个堆的空间更快。

下面通过例子来说明基于代的垃圾回收的思想:

1.首先,初始时堆空间中没有对象,当对象开始被分配到堆中时,会被分配到0代堆空间中,初始只有0代。CLR初始化时会为0代空间分配相应的大小,几k字节。如下图所示ABCDE被分配到0代空间:

程序运行一段时间后,C,E不可达,当要分配对象F时,由于0代已经没有空间可供分配,这时需要执行垃圾回收,回收CE,与此同时执行压缩,将ABD存放到连续空间,与此同时,ABD被提升为1代空间。执行垃圾收集后的堆空间如下所示:

垃圾收集后,可以看到0代空间已经没有对象,因此新的对象总是会被分配到0代空间中,接下来当我们在分配F到K的对象时,堆的空间如下图所示:

程序运行一段时间后,我们在分配新的对象L给0代空间时,0代空间已满,现在需要执行GC, (在这里需要说明的时CLR初始化时也会分配相应空间的大小给1代空间,1代空间分配的大小要大于0代空间)。GC在执行垃圾收集时首先会检查1代空间已使用的大小是否已经达到分配的大小,没有的话不执行垃圾对象的检查。因此垃圾收集只检查0代空间的对象,发现H和J不可用,可以回收,经过压缩和提升后,堆空间中的分配如下图所示:

可以看到1代空间中的B随然不可达,但是没有被回收,垃圾收集器只回收了0代空间中的不可用的对象,接下来我们在分配L到0的对象到0代内存空间中,分配后堆空间如下图所示:

当我们再次分配对象P时,0代空间已满,执行GC,同上面一样,执行GC后堆空间如下所示:

可以看到1代的被分配的空间在不断增大,不可达对象在缓慢增多。让我们再次分配对象P到S,分配后堆空间图如下所示:

当我们分配对象T时,由于0代空间已满,需要执行GC,假设1代对象分配所占空间已达到最大的分配空间,这时GC会检查1代堆空间中的对象以及0代堆空间中的对象,回收不可用对象,并执行压缩,回收后的堆空间如下图所示:

由图可以看到,1代空间中幸存的对象被提到2代空间中,0代空间中的对象被提到2代空间中,0代空间为空。

通过以上的例子可以看到,垃圾收集器会多次回收0代堆空间中的对象后才执行1代内存空间中的对象回收,这样做的目的主要是为了提供程序的性能。通常情况下,具有更长时间的对象具有更长的生命周期。同时,CLR垃圾回收算法也是自适应的,他会根据每个代空间中对象的分配后的生命期以及每次回收对象时的回收的程度,来动态调整代空间的大小以及相应的执行1代或2代堆空间的回收。比如,0代空间中的对象都是垃圾时,可以不执行压缩,直接将nextObj指向第一个对象的位。当发现0代空间中的对象执行GC时回收的对象很少时,可以增大0代空间的大小,当0代空间的对象回收比较多时,可以调小0代空间的大小等等。

参考资料:CLR VIA C# BOOK

时间: 2024-08-03 02:03:58

CLR 垃圾回收算法的相关文章

垃圾回收算法之引用跟踪算法

在<垃圾回收算法之引用计数算法>这篇博客里,我们说到了引用计数算法的缺陷:会造成循环引用的问题.本篇的引用跟踪算法则客服了这种缺陷. 在引用计数算法中,对于每个引用的对象,我们有一个额外的字段专门计数这个对象被引用的次数,当次数为0时,则视为垃圾.那在引用跟踪算法中,我们如何判断这个对象是垃圾呢?答案是:对象可达图. 一.对象可达图 它的含义是,首先将所有的对象标记为垃圾并将它们视为根,从根出发,将所有的正在使用的对象串联起来,直至完毕,不被串联的对象即为"不可达",即被视

java----java垃圾回收算法

1.引用计数法(Reference Counting Collector) 1.1算法分析 引用计数是垃圾收集器中的早期策略.在这种方法中,堆中每个对象实例都有一个引用计数.当一个对象被创建时,且将该对象实例分配给一个变量,该变量计数设置为1.当任何其它变量被赋值为这个对象的引用时,计数加1(a = b,则b引用的对象实例的计数器+1),但当一个对象实例的某个引用超过了生命周期或者被设置为一个新值时,对象实例的引用计数器减1.任何引用计数器为0的对象实例可以被当作垃圾收集.当一个对象实例被垃圾收

垃圾回收算法(5)分代回收

分代垃圾回收,基于的是“大部分的对象,在生成后马上就会变成垃圾”这一经验上的事实为设计出发点.此前讨论过基于引事实的另一个垃圾回收算法,引用计数出的一些优化思路. 分代的关键是: 给对象记录下一个age,随着每一次垃圾回收,这个age会增加: 给不同age的对象分配不同的堆内内存空间,称为某一代: 对某一代的空间,有适合其的垃圾回收算法: 对每代进行不同垃圾回收,一般会需要一个额外的信息:即每代中对象被其他代中对象引用的信息.这个引用信息对于当前代来说,扮演与"root"一样的角色,也

常用垃圾回收算法

今天我关于常见的垃圾回收算法来做个总结,我们最常听到的是Java虚拟机里的垃圾回收机制,其实垃圾回收的概念最先并不是Java里首先提出来的, 垃圾回收这个概念很早就已经被提出来了,并且已经在其他语言中得到了应用. 关于垃圾回收的机制,这里不再解释,这篇文章我主要介绍常见的垃圾回收算法,当然还有其他的. 算法一:引用计数法. 这个方法是最经典点的一种方法.具体是对于对象设置一个引用计数器,每增加一个变量对它的引用,引用计数器就会加1,没减少一个变量的引用, 引用计数器就会减1,只有当对象的引用计数

常用的垃圾回收算法

引用计数法 对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就加1,当引用失效时,引用计数器就减1. 只要A对象的引用计数器的值为0,则对象A就不可能再被使用. 实现也很简单,只需要为每个对象配备一个整型的计数器即可. 缺点: 1.无法处理循环引用的情况 2.引用计算器要求在每次引用产生和消除的时候,需要伴随一个加法操作和减法操作,对系统性能有一定影响. A引用了B,B又引用了A,因此A和B的引用计数器都不为0,但是系统中却 不存在任何第3个对象引用了A和B. 这种情况下,A和B是无

可视化垃圾回收算法

链接:blog.jobbole.com/77280/ github.com/kenfox/gc-viz 大部分开发者都认为自动垃圾回收器是理所当然的.实际上,这只是语言运行时提供的一项实用功能,旨在简化我们的开发工作. 但是如果尝试着了解垃圾回收器的内部原理,你会发现很难弄明白.除非熟悉它的工作流程和错误处理方式,否则内部成千上万的实现细节会让你不知所措. 我编译了一个有五种不同的垃圾回收算法工具.程序运行时会创建一个动画界面.你可以从github.com/kenfox/gc-viz上获取动画和

CLR垃圾回收的设计

CLR垃圾回收的设计 作者: Maoni Stephens (@maoni0) - 2015 附: 关于垃圾回收的信息,可以参照本文末尾资源章节里引用的垃圾回收手册一书. 组件架构 GC包含的两个组件分别是内存分配器和垃圾收集器.内存分配器负责获取更多的内存并在适当的时候触发垃圾收集.垃圾收集器回收程序中不再使用的对象的内存. 有多种方法调用垃圾回收器,例如人工调用GC.Collect或者当终结线程在接收到表示低内存的异步通知时(调用). 内存分配器的设计 内存分配器由执行引擎(EE)的内存分配

JAVA虚拟机垃圾回收算法原理

除了释放不再被引用的对象外,垃圾收集器还要处理堆碎块.新的对象分配了空间,不再被引用的对象被释放,所以堆内存的空闲位置介于活动的对象之间.请求分配新对象时可能不得不增大堆空间的大小,虽然可以使用的总空闲空间是足够的.这是因为,堆中没有连续的空闲空间放得下新的对象. 垃圾收集器算法 任何垃圾回收算法都必须做两件事,首先,它必须检测出垃圾对象.其次,它必须回收垃圾对象所使用的堆空间并还给程序.从根对象开始,任何可以被触及的对象都被认为是“活动的”对象(如果正在运行的程序可以访问到根对象和某个对象之间

JVM垃圾回收算法 总结及汇总

先看一眼JVM虚拟机运行时的内存模型: 1.方法区 Perm(永久代.非堆) 2.虚拟机栈 3.本地方法栈 (Native方法) 4.堆 5.程序计数器 1 首先的问题是:jvm如何知道那些对象需要回收 ? 目前两种标识算法.三种回收算法.两种清除算法.三种收集器 引用计数法 每个对象上都有一个引用计数,对象每被引用一次,引用计数器就+1,对象引用被释放,引用计数器-1,直到对象的引用计数为0,对象就标识可以回收 这个可以用数据算法中的图形表示,对象A-对象B-对象C 都有引用,所以不会被回收,