GC的前世与此生程序员
虽然本文是以.net做为目标来说述GC,可是GC的概念并不是才诞生不久。早在1958年,由鼎鼎大名的图林奖得主John McCarthy所实现的Lisp语言就已经提供了GC的功能,这是GC的第一次出现。Lisp的程序员认为内存管理过重要了,因此不能由程序员本身来管理。但后来的日子里Lisp却没有成气候,采用内存手动管理的语言占据了上风,以C为表明。出于一样的理由,不一样的人却又不一样的见解,C程序员认为内存管理过重要了,因此不能由系统来管理,而且讥笑Lisp程序慢如乌龟的运行速度。的确,在那个对每个Byte都要精心计算的年代GC的速度和对系统资源的大量占用使不少人的没法接受。然后,1984年由Dave Ungar开发的Small talk语言第一次采用了Generational garbage collection的技术(这个技术在下文中会谈到),可是Small talk也没有获得十分普遍的应用。
直到20世纪90年代中期GC才以主角的身份登上了历史的舞台,这不得不归功于Java的进步,今日的GC已非吴下阿蒙。Java采用VM(Virtual Machine)机制,由VM来管理程序的运行固然也包括对GC管理。90年代末期.net出现了,.net采用了和Java相似的方法由CLR(Common Language Runtime)来管理。这两大阵营的出现将人们引入了以虚拟平台为基础的开发时代,GC也在这个时候愈来愈获得大众的关注。
为何要使用GC呢?也能够说是为何要使用内存自动管理?有下面的几个缘由:
一、提升了软件开发的抽象度;
二、程序员能够将精力集中在实际的问题上而不用分心来管理内存的问题;
三、可使模块的接口更加的清晰,减少模块间的偶合;
四、大大减小了内存人为管理不当所带来的Bug;
五、使内存管理更加高效。
总的说来就是GC可使程序员能够从复杂的内存问题中摆脱出来,从而提升了软件开发的速度、质量和安全性。 算法
什么是GCspring
GC如其名,就是垃圾收集,固然这里仅就内存而言。Garbage Collector(垃圾收集器,在不至于混淆的状况下也成为GC)以应用程序的root为基础,遍历应用程序在Heap上动态分配的全部对象[2],经过识别它们是否被引用来肯定哪些对象是已经死亡的哪些仍须要被使用。已经再也不被应用程序的root或者别的对象所引用的对象就是已经死亡的对象,即所谓的垃圾,须要被回收。这就是GC工做的原理。为了实现这个原理,GC有多种算法。比较常见的算法有Reference Counting,Mark Sweep,Copy Collection等等。目前主流的虚拟系统.net CLR,Java VM和Rotor都是采用的Mark Sweep算法。安全
1、Mark-Compact 标记压缩算法
简单把.NET的GC算法看做Mark-Compact算法
阶段1: Mark-Sweep 标记清除阶段
先假设heap中全部对象均可以回收,而后找出不能回收的对象,给这些对象打上标记,最后heap中没有打标记的对象都是能够被回收的
阶段2: Compact 压缩阶段
对象回收以后heap内存空间变得不连续,在heap中移动这些对象,使他们从新从heap基地址开始连续排列,相似于磁盘空间的碎片整理
Heap内存通过回收、压缩以后,能够继续采用前面的heap内存分配方法,即仅用一个指针记录heap分配的起始地址就能够
主要处理步骤:将线程挂起=>肯定roots=>建立reachable objectsgraph=>对象回收=>heap压缩=>指针修复
能够这样理解roots:heap中对象的引用关系错综复杂(交叉引用、循环引用),造成复杂的graph,roots是CLR在heap以外能够找到的各类入口点。GC搜索roots的地方包括全局对象、静态变量、局部对象、函数调用参数、当前CPU寄存器中的对象指针(还有finalizationqueue)等。主要能够归为2种类型:已经初始化了的静态变量、线程仍在使用的对象(stack+CPU register)
Reachable objects:指根据对象引用关系,从roots出发能够到达的对象。例如当前执行函数的局部变量对象A是一个rootobject,他的成员变量引用了对象B,则B是一个reachable object。从roots出发能够建立reachable objectsgraph,剩余对象即为unreachable,能够被回收
指针修复是由于compact过程移动了heap对象,对象地址发生变化,须要修复全部引用指针,包括stack、CPUregister中的指针以及heap中其余对象的引用指针
Debug和release执行模式之间稍有区别,release模式下后续代码没有引用的对象是unreachable的,而debug模式下须要等到当前函数执行完毕,这些对象才会成为unreachable,目的是为了调试时跟踪局部对象的内容
传给了COM+的托管对象也会成为root,而且具备一个引用计数器以兼容COM+的内存管理机制,引用计数器为0时这些对象才可能成为被回收对象
Pinnedobjects指分配以后不能移动位置的对象,例如传递给非托管代码的对象(或者使用了fixed关键字),GC在指针修复时没法修改非托管代码中的引用指针,所以将这些对象移动将发生异常。pinnedobjects会致使heap出现碎片,但大部分状况来讲传给非托管代码的对象应当在GC时可以被回收掉
2、 Generational 分代算法
程序可能使用几百M、几G的内存,对这样的内存区域进行GC操做成本很高,分代算法具有必定统计学基础,对GC的性能改善效果比较明显
将对象按照生命周期分红新的、老的,根据统计分布规律所反映的结果,能够对新、老区域采用不一样的回收策略和算法,增强对新区域的回收处理力度,争取在较短期间隔、较小的内存区域内,以较低成本将执行路径上大量新近抛弃再也不使用的局部对象及时回收掉
分代算法的假设前提条件:
一、大量新建立的对象生命周期都比较短,而较老的对象生命周期会更长
二、对部份内存进行回收比基于所有内存的回收操做要快
三、新建立的对象之间关联程度一般较强。heap分配的对象是连续的,关联度较强有利于提升CPU cache的命中率
.NET将heap分红3个代龄区域: Gen 0、Gen 一、Gen 2
Heap分为3个代龄区域,相应的GC有3种方式: # Gen 0 collections, # Gen 1 collections, #Gen 2 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的成本很是低,2代GC称为fullGC,一般成本很高。粗略的计算0代和1代GC应当能在几毫秒到几十毫秒之间完成,Gen 2 heap比较大时fullGC可能须要花费几秒时间。大体上来说.NET应用运行期间2代、1代和0代GC的频率应当大体为1:10:100。ide
做者:spring yang函数
出处:http://www.cnblogs.com/springyangwc/post
本文版权归做者和博客园共有,欢迎转载,但未经做者赞成必须保留此段声明,且在文章页面明显位置给出原文链接,不然保留追究法律责任的权利。 性能