做为.NET进阶内容的一部分,垃圾回收器(简称GC)是必须了解的内容。本着“通俗易懂”的原则,本文将解释CLR中垃圾回收器的工做原理。算法
先来看MSDN的解释:初始化新进程时,运行时会为进程保留一个连续的地址空间区域。这个保留的地址空间被称为托管堆。编程
"托管堆也是堆",为何这样说呢?这么说是但愿你们不要被“术语”迷惑,这个知识点的前提是“值类型和引用类型的区别”。这里假设读者已经知道“值类型存储在栈中,引用类型存储在堆中。(引用类型的引用存储在栈中)”这一重要概念。因此,根据这个理论,除值类型外,CLR要求全部资源都从托管堆分配。缓存
托管堆维护着一个指针,这里命名为NextObjPtr,它指向下一个对象在堆中的分配位置。编程语言
这个是计算机基础知识,这里复习一下,有助于对下面“根”概念的理解。性能
CPU寄存器是CPU本身的"临时存储器",比内存的存取还快。按与CPU远近来分,离得最近的是寄存器,而后缓存(计算机1、2、三级缓存),最后内存。优化
类中定义的任何静态字段,方法的参数,局部变量(仅限引用类型变量)等都是根,另外cpu寄存器中的对象指针也是根。根是CLR在堆以外能够找到的各类入口点。指针
若是一个根引用了堆中的一个对象,则该对象为“可达”,不然便是“不可达”。对象
从计算机组成的角度来说,全部的程序都是要驻留在内存中运行的。而内存是一个限制因素(大小)。除此以外,托管堆也有大小限制。若是托管堆没有大小限制,那C#的执行速度要优于c了(托管堆的结构让它有比c运行时堆更快的对象分配速度)。由于地址空间和存储的限制因素,托管堆要经过垃圾回收机制,来维持它的正常运做,保证对象的分配,不会“内存溢出”。blog
回收分为两个阶段: 标记 --> 压缩进程
标记的过程,其实就是判断对象是否可达的过程。当全部的根都检查完毕后,堆中将包含可达(已标记)与不可达(未标记)对象。
标记完成后,进入压缩阶段。在这个阶段中,垃圾回收器线性的遍历堆,以寻找不可达对象的连续内存块。并把可达对象移动到这里以压缩堆。这个过程有点相似于磁盘空间的碎片整理。
如上图所示,绿色框表示可达对象,黄色框为不可达对象。不可达对象清除后,移动可达对象实现内存压缩(变得更紧凑)。
压缩以后,“指向这些对象的指针”的变量和CPU寄存器如今都会失效,垃圾回收器必须从新访问全部根,并修改它们来指向对象的新内存位置。这会形成显著的性能损失。这个损失也是托管堆的主要缺点。
基于以上特色,垃圾回收引起的回收算法也是一项研究课题。由于若是真等到托管堆满才开始执行垃圾回收,那就真的太“慢”了。
代是CLR垃圾回收器采用的一种机制,它惟一的目的就是提高应用程序的性能。分代回收,速度显然快于回收整个堆。
CLR托管堆支持3代:第0代,第1代,第2代。第0代的空间约为256KB,第1代约为2M,第2代约为10M。新构造的对象会被分配到第0代,
如上图所示,当第0代的空间满时,垃圾回收器启动回收,不可达对象(上图C、E)会被回收,存活的对象被归为第1代。
当第0代空间已满,第1代也开始有不少不可达对象以致空间将满时,这时两代垃圾都将被回收。存活下来的对象(可达对象),第0代升为第1代,第1代升为第2代。
实际CLR的代回收机制更加“智能”,若是新建立的对象生存周期很短,第0代垃圾也会马上被垃圾回收器回收(不用等空间分配满)。另外,若是回收了第0代,发现还有不少对象“可达”,
并无释放多少内存,就会增大第0代的预算至512KB,回收效果就会转变为:垃圾回收的次数将减小,但每次都会回收大量的内存。若是尚未释放多少内存,垃圾回收器将执行
彻底回收(3代),若是仍是不够,则会抛出“内存溢出”异常。
也就是说,垃圾回收器会根据回收内存的大小,动态的调整每一代的分配空间预算!达到自动优化!
垃圾回收背后有这样一个基本的观念:编程语言(大多数的)彷佛总能访问无限的内存。而开发者能够一直分配、分配再分配——像魔法同样,取之不尽用之不竭。
.NET垃圾回收器的基本工做原理是:经过最基本的标记清除原理,清除不可达对象;再像磁盘碎片整理同样压缩、整理可用内存;最后经过分代算法实现性能最优化。
注:若是此文对您有帮助,请点击右下方的“推荐”。
如需转载,请注明出处。