之前一直觉得gc的原理很简单,也就是分代处理堆数据,直到个人膝盖中了一箭(好吧 直到有天汪涛和我说他面试携程的面试题 关于服务器和 工做站gc 的区别)其实我当时尚不知道 工做站和服务器有什么区别更不要提其中的GC。html
话很少说 下面开始谈论GC程序员
一GC的前世此生 ,二.NET垃圾回收机制面试
参考文献算法
http://www.myexception.cn/c-sharp/1515938.html编程
http://blog.csdn.net/bingbing200x安全
http://blog.csdn.net/lerit/article/details/4451287服务器
一.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也在这个时候愈来愈获得大众的关注。
二.NET垃圾回收机制
1.导言
2.关于垃圾回收
3.垃圾回收的功能
4.托管堆和托管栈
5.垃圾收集器如何寻找再也不使用的托管对象并释放其占用的内存
a.根
b.建立一个图 一个描述对象间引用关系的图
在.NET Framework中,内存中的资源(即全部二进制信息的集合)分为"托管资源"和"非托管资源".托管资源必须接受.NET Framework的CLR(通用语言运行时)的管理(诸如内存类型安全性检查),而非托管资源则没必要接受.NET Framework的CLR管理. (了解更多区别请参阅.NET Framework或C#的高级编程资料)
托管资源在.NET Framework中又分别存放在两种地方: "堆栈"和"托管堆"(如下简称"堆");规则是,全部的值类型(包括引用和对象实例)和引用类型的引用都存放在"堆栈"中,而全部引用所表明的对象实例都保存在堆中。
在.NET中,释放托管资源是能够自动经过"垃圾回收器"完成的(注意,"垃圾回收"机制是.NET Framework的特性,而不是C#的),但具体来讲,仍有些须要注意的地方:
1.值类型和引用类型的引用实际上是不须要什么"垃圾回收器"来释放内存的,由于当它们出了做用域后会自动释放所占内存(由于它们都保存在"堆栈"中,学过数据结构可知这是一种先进后出的结构);
2.只有引用类型的引用所指向的对象实例才保存在"堆"中,而堆由于是一个自由存储空间,因此它并无像"堆栈"那样有生存期("堆栈"的元素弹出后就代 表生存期结束,也就表明释放了内存),而且很是要注意的是,"垃圾回收器"只对这块区域起做用;
3."垃圾回收器"也许并不像许多人想象的同样会当即执行(当堆中的资源须要释放时),而是在引用类型的引用被删除和它在"堆"中的对象实例被删除中间有 个间隔,为何呢? 由于"垃圾回收器"的调用是比较消耗系统资源的,所以不可能常常被调用;
(固然,用户代码能够用方法System.GC.Collect()来强制执行"垃圾回收器")
4.有析构函数的对象须要垃圾收集器两次处理才能删除:第一次调用析构函数时,没有删除对象,第二次调用才真正删除对象;
5.因为垃圾收集器的工做方式,没法肯定C#对象的析构函数什么时候执行;
6.可实现IDisposable接口的Dispose()来显示释放由对象使用的全部未托管资源;
7.垃圾收集器在释放了它能释放的全部对象后,就会压缩其余对象,把他们都移动回托管堆的端部,再次造成一个连续的块。
导言
垃圾回收(Garbage Collection)在.net中是一个很重要的机制。本文将要谈到CLR4.0对垃圾回收作了哪些改进。为了更好地理解这些改进, 本文也要介绍垃圾回收的历史。这样咱们对整个垃圾回收有一个大的印象。这个大印象对于咱们掌握.net架构是有帮助的。
关于垃圾回收
在C++时代,咱们须要本身来管理申请内存和释放内存. 因而有了new, delete关键字. 还有的一些内存申请和释放函数(malloc/free)。C++程序必须很好地管理本身的内存,否则就会形成内存泄漏(Memory leak)。在.net时代, 微软为开发人员提供了一个强有力的机制--垃圾回收,垃圾回收机制是CLR的一部分, 咱们不用操心内存什么时候释放,咱们能够花更多精力关注应用程序的业务逻辑。CLR里面的垃圾回收机制用必定的算法判断某些内存程序再也不使用,回收这些内存并交给咱们的程序再使用.
垃圾回收的功能
用来管理托管资源和非托管资源所占用的内存分配和释放。
寻找再也不使用的对象,释放其占用的内存, 以及释放非托管资源所占用的内存。
垃圾回收器释放内存以后, 出现了内存碎片, 垃圾回收器移动一些对象,以获得整块的内存,同时全部的对象引用都将被调整为指向对象新的存储位置。
下面咱们来看看CLR是如何管理托管资源的。
托管堆和托管栈
.net CLR在运行咱们的程序时,在内存中开辟了两块地方做不一样的用处--托管栈和托管堆. 托管栈用来存放局部变量, 跟踪程序调用与返回。托管堆用来存放引用类型。引用类型老是存放于托管堆。值类型一般是放在托管栈上面的. 若是一个值类型是一个引用类型的一部分,则此值类型随该引用类型存放于托管堆中。哪些东西是值类型? 就是定义于System.ValueType之下的这些类型:
bool byte char decimal double enum float int long sbyte short struct uint ulong ushort
什么是引用类型呢? 只要用class, interface, delegate, object, string声明的类型, 就是引用类型。
咱们定义一个局部变量, 其类型是引用类型。当咱们给它赋一个值,以下例:
private void MyMethod()
{
MyType myType = new MyType();
myType.DoSomeThing();
}
在此例中, myType 是局部变量, new实例化出来的对象存储于托管堆, 而myType变量(引用部分)存储于托管栈。在托管栈的myType变量存储了一个指向托管堆上new实例化出来对象的引用。CLR运行此方法时, 将托管栈指针移动, 为局部变量myType分配空间, 当执行new时, CLR先查看托管堆是否有足够空间, 足够的话就只是简单地移动下托管堆的指针,来为MyType对象分配空间,若是托管堆没有足够空间,会引发垃圾收集器工做。CLR在分配空间以前,知道全部类型的元数据,因此能知道每一个类型的大小,即占用空间的大小。
当CLR完成MyMethod方法的执行时, 托管栈上的myType局部变量被当即删除, 可是托管堆上的MyType对象却不必定立刻删除。这取决于垃圾收集器的触发条件。后面要介绍此触发条件。
上面咱们了解了CLR如何管理托管资源。下面咱们来看垃圾收集器如何寻找再也不使用的托管对象,并释放其占用的内存。
垃圾收集器如何寻找再也不使用的托管对象,并释放其占用的内存
前面咱们了解了CLR如何管理托管栈上的对象。按照先进后出原则便可比较容易地管理托管栈的内存。托管堆的管理比托管栈的管理复杂多了。下面所谈都是针对托管堆的管理。
根
垃圾收集器寻找再也不使用的托管对象时, 其判断依据是当一个对象再也不有引用指向它, 就说明此对象是能够释放了。一些复杂的状况下能够出现一个对象指向第二个对象,第二个对象指向第三个对象,…就象一个链表。那么,垃圾收集器从哪里开始查找再也不使用的托管对象呢? 以刚才所说的链表为例,显然是应该从链表的开头开始查找。那么,在链表开头的是些什么东东呢?
是局部变量, 全局变量, 静态变量, 指向托管堆的CPU寄存器。在CLR中,它们被称之为根。
有了开始点,垃圾收集器接下来怎么作呢?
建立一个图, 一个描述对象间引用关系的图.
垃圾收集器首先假定全部在托管堆里面的对象都是不可到达的(或者说没有被引用的,再也不须要的), 而后从根上的那些变量开始, 针对每个根上的变量,找出其引用的托管堆上的对象,将找到的对象加入这个图, 而后再沿着这个对象往下找,看看它有没有引用另一个对象,有的话,继续将找到的对象加入图中,若是没有的话,就说明这条链已经找到尾部了。垃圾收集器就去从根上的另一个变量开始找,直到根上的全部变量都找过了, 而后垃圾收集器才中止查找。值得一提的是,在查找过程当中, 垃圾收集器有些小的优化,如: 因为对象间的引用关系多是比较复杂的, 因此有可能找到一个对象, 而此对象已经加入图了, 那么垃圾收集器就再也不在此条链上继续查找, 转去其余的链上继续找。这样对垃圾收集器的性能有所改善。
内存释放和压缩
建立对象引用图以后,垃圾回收器将那些没有在这个图中的对象(即再也不须要的对象)释放。释放内存以后, 出现了内存碎片, 垃圾回收器扫描托管堆,找到连续的内存块,而后移动未回收的对象到更低的地址, 以获得整块的内存,同时全部的对象引用都将被调整为指向对象新的存储位置。这就象一个夯实的动做。也就是说,一个对象即便没有被清除,因为内存压缩,致使他的引用位置发生变化。
下面要说到的是代的概念。代概念的引入是为了提升垃圾收集器的总体性能。
代 Gen
请想想若是垃圾收集器每次老是扫描全部托管堆中的对象,对性能会有什么影响。会不会很慢?是的。微软所以引入了代的概念。
为何代的概念能够提升垃圾收集器的性能?由于微软是基于对大量编程实践的科学估计,作了一些假定而这些假定符合绝大多数的编程实践:
越新的对象,其生命周期越短。
越老的对象,其生命周越长。
新对象之间一般有强的关系并被同时访问。
压缩一部分堆比压缩整个堆快。
有了代的概念,垃圾回收活动就能够大部分局限于一个较小的区域来进行。这样就对垃圾回收的性能有所提升。
让咱们来看垃圾收集器具体是怎么实现代的:
第0代:新建对象和从未通过垃圾回收对象的集合
第1代:在第0代收集活动中未回收的对象集合
第2代:在第1和第2代中未回收的对象集合, 即垃圾收集器最高只支持到第2代, 若是某个对象在第2代的回收活动中留下来,它仍呆在第2代的内存中。
当程序刚开始运行,垃圾收集器分配为每一代分配了必定的内存,这些内存的初始大小由.net framework的策略决定。垃圾收集器记录了这三代的内存起始地址和大小。这三代的内存是链接在一块儿的。第2代的内存在第1代内存之下,第1代内存在第0代内存之下。应用程序分配新的托管对象老是从第0代中分配。若是第0代中内存足够,CLR就很简单快速地移动一下指针,完成内存的分配。这是很快速的。当第0代内存不足以容纳新的对象时,就触发垃圾收集器工做,来回收第0代中再也不须要的对象,当回收完毕,垃圾收集器就夯实第0代中没有回收的对象至低的地址,同时移动指针至空闲空间的开始地址(同时按照移动后的地址去更新那些相关引用),此时第0代就空了,由于那些在第0代中没有回收的对象都移到了第1代。
当只对第0代进行收集时,所发生的就是部分收集。这与以前所说的所有收集有所区别(由于代的引入)。对第0代收集时,一样是从根开始找那些正引用的对象,但接下来的步骤有所不一样。当垃圾收集器找到一个指向第1代或者第2代地址的根,垃圾收集器就忽略此根,继续找其余根,若是找到一个指向第0代对象的根,就将此对象加入图。这样就能够只处理第0代内存中的垃圾。这样作有个先决条件,就是应用程序此前没有去写第1代和第2代的内存,没有让第1代或者第2代中某个对象指向第0代的内存。可是实际中应用程序是有可能写第1代或者第2代的内存的。针对这种状况,CLR有专门的数据结构(Card table)来标志应用程序是否曾经写第1代或者第2代的内存。若是在这次对第0代进行收集以前,应用程序写过第1代或者第2代的内存,那些被Card Table登记的对象(在第1代或者第2代)将也要在这次对第0代收集时做为根。这样,才能够正确地对第0代进行收集。
以上说到了第0代收集发生的一个条件,即第0代没有足够内存去容纳新对象。执行GC.Collect()也会触发对第0代的收集。另外,垃圾收集器还为每一代都维护着一个监视阀值。第0代内存达到这个第0代的阀值时也会触发对第0代的收集。对第1代的收集发生在执行GC.Collect(1)或者第1代内存达到第1代的阀值时。第2代也有相似的触发条件。当第1代收集时,第0代也须要收集。当第2代收集时,第1和第0代也须要收集。在第n代收集以后仍然存留下来的对象将被转移到第n+1代的内存中,若是n=2, 那么存留下来的对象还将留在第2代中。
对象结束
对象结束机制是程序员忘记用Close或者Dispose等方法清理申请的资源时的一个保证措施。以下的一个类,当一个此类的实例建立时,在第0代中分配内存,同时此对象的引用要被加入到一个由CLR维护的结束队列中去。
[c-sharp] view plaincopyprint?
public class BaseObj
{
public BaseObj()
{
}
protected override void Finalize()
{
// Perform resource cleanup code here...
// Example: Close file/Close network connection Console.WriteLine("In Finalize.");
}
}
当此对象成为垃圾时,垃圾收集器将其引用从结束队列移到待结束队列中,同时此对象会被加入引用关系图。一个独立运行的CLR线程将一个个从待结束队列(Jeffrey Richter称之为Freachable queue)取出对象,执行其Finalize方法以清理资源。所以,此对象不会立刻被垃圾收集器回收。只有当此对象的Finalize方法被执行完毕后,其引用才会从待结束队列中移除。等下一轮回收时,垃圾回收器才会将其回收。
GC类有两个公共静态方法GC.ReRegisterForFinalize和GC.SuppressFinalize你们也许想了解一下,ReRegisterForFinalize是将指向对象的引用添加到结束队列中(即代表此对象须要结束),SuppressFinalize是将结束队列中该对象的引用移除,CLR将再也不会执行其Finalize方法。
由于有Finalize方法的对象在new时就自动会加入结束队列中,因此ReRegisterForFinalize能够用的场合比较少。ReRegisterForFinalize比较典型的是配合重生(Resurrection)的场合来用。重生指的是在Finalize方法中让根又从新指向此对象。那么此对象又成了可到达的对象,不会被垃圾收集器收集,可是此对象的引用未被加入结束队列中。因此此处须要用ReRegisterForFinalize方法来将对象的引用添加到结束队列中。由于重生自己在现实应用中就不多见,因此ReRegisterForFinalize也将比较少用到。
相比之下,SuppressFinalize更经常使用些。SuppressFinalize用于同时实现了Finalize方法和Dispose()方法来释放资源的状况下。在Dispose()方法中调用GC.SuppressFinalize(this),那么CLR就不会执行Finalize方法。Finalize方法是程序员忘记用Close或者Dispose等方法清理资源时的一个保证措施。若是程序员记得调用Dispose(),那么就会不执行Finalize()来再次释放资源;若是程序员忘记调用Dispose(), Finalize方法将是最后一个保证资源释放的措施。这样作不失为一种双保险的方案。
对象结束机制对垃圾收集器的性能影响比较大,同时CLR难以保证调用Finalize方法的时间和次序。所以,尽可能不要用对象结束机制,而采用自定义的方法或者名为Close,Dispose的方法来清理资源。能够考虑实现IDisposable接口并为Dispose方法写好清理资源的方法体。
大对象堆
大对象堆专用于存放大于85000字节的对象。初始的大对象内存区域堆一般在第0代内存之上,而且与第0代内存不邻接。第0,第1和第2代合起来称为小对象堆。CLR分配一个新的对象时,若是其大小小于85000字节,就在第0代中分配,若是其大小大于等于85000字节,就在大对象堆中分配。
由于大对象的尺寸比较大,收集时成本比较高,因此对大对象的收集是在第2代收集时。大对象的收集也是从根开始查找可到达对象,那些不可到达的大对象就可回收。垃圾收集器回收了大对象后,不会对大对象堆进行夯实操做(也就是碎片整理,毕竟移动大对象成本较高),而是用一个空闲对象表的数据结构来登记哪些对象的空间能够再利用,其中两个相邻的大对象回收将在空闲对象表中做为一个对象对待。空闲对象表登记的空间将能够再分配新的大对象。
大对象的分配,回收的成本都较小对象高,所以在实践中最好避免很快地分配大对象又很快回收,能够考虑如何分配一个大对象池,重复利用这个大对象池,而不频繁地回收。
弱引用
弱引用是相对强引用来讲的。强引用指的是根有一个指针指向对象。弱引用是经过对强引用加以弱化而获得的。这个弱化的手段就是用System.WeakReference类。因此精确地说,强引用指的是根有一个非WeakReference类型的指针指向对象,而弱引用就是根有一个WeakReference类型的指针指向对象。垃圾收集器看到一个WeakReference类型的根指向某个对象,就会特别处理。因此在垃圾收集器建立对象引用关系图的时候,若是遇到一个弱引用指针,那么垃圾收集器就不会将其加入图中。若是一个对象只有弱引用指向它,那么垃圾收集器能够收集此对象。一旦将一个强引用加到对象上,无论对象有没有弱引用,对象都不可回收。
垃圾收集器对WeakReference类的特别处理从new操做就开始。一般的类,只要new操做,就会从托管堆分配空间,而WeakReference类的new操做不是这样作的。咱们先来看WeakReference类的构造函数:
WeakReference(Object target);
WeakReference(Object target, Boolean trackResurrection);
此二构造函数都须要一个对象的引用,第二个构造函数还须要一个布尔值参数来表示咱们是否须要跟踪对象的重生。此参数的意义后文会交代。
假设咱们有两个类MyClass和MyAnotherClass,都有Finalize方法。咱们声明两个对象:
MyClass myObject = new MyClass();
MyAnotherClass myAnotherObject = new MyAnotherClass();
当咱们用这样的代码声明一个弱引用对象: WeakReference myShortWeakReferenceObject = new WeakReference( myObject );
垃圾收集器内部有一个短弱引用表,用这样声明的弱引用对象将不会在托管堆中分配空间,而是在短弱引用表中分配一个槽。此槽中记录对myObject的引用。New操做将此槽的地址返回给myShortWeakReferenceObject变量。
若是咱们用这样的代码声明一个弱引用对象(咱们要跟踪该对象的重生): WeakReference myLongWeakReferenceObject = new WeakReference( myAnotherObject, true );
垃圾收集器内部有一个长弱引用表,用这样声明的弱引用对象将不会在托管堆中分配空间,而是在长弱引用表中分配一个槽。此槽中记录对myAnotherObject的引用。New操做将此槽的地址返回给myLongWeakReferenceObject变量。
垃圾收集器此时的收集流程是这样的:
1. 垃圾收集器创建对象引用图,来找到全部的可到达对象。前文已经说过如何创建图。特别的地方是,若是遇到非WeakReference指针,就加入图,若是遇到WeakReference指针,就不加入图。这样图就建好了。
2. 垃圾收集器扫描短弱引用表。若是一个指针指向一个不在图中的对象,那么此对象就是一个不可到达的对象,垃圾收集器就将短弱引用表相应的槽置空。
3. 垃圾收集器扫描结束队列。若是队列中一个指针指向一个不在图中的对象,此指针将被从结束队列移到待结束队列,同时此对象被加入引用关系图中,由于此时此对象是Finalize可到达的。
4. 垃圾收集器扫描长弱引用表。若是一个指针指向一个不在图中的对象(注意此时图中已包含Finalize可到达的对象),那么此对象就是一个不可到达的对象,垃圾收集器就将长弱引用表相应的槽置空。
5. 垃圾收集器夯实(压缩)托管堆。
短弱引用不跟踪重生。即垃圾收集器发现一个对象为不可到达就当即将短弱引用表相应的槽置空。若是该对象有Finalize方法,而且Finalize方法尚未执行,因此该对象就还存在。若是应用程序访问弱引用对象的Target属性,即便该对象还存在,也会获得null。
长弱引用跟踪重生。即垃圾收集器发现一个对象是Finalize可到达的对象,就不将相应的槽置空。由于Finalize方法尚未执行,因此该对象就还存在。若是应用程序访问弱引用对象的Target属性,能够获得该对象;可是若是Finalize方法已经被执行,就代表该对象没有重生。
按照上面的例子,若是执行以下代码会发生什么呢?
[c-sharp] view plaincopyprint?
//File: MyClass.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace ConsoleApplication2
{
class MyClass
{
~MyClass()
{
Console.WriteLine("In MyClass destructor+++++++++++++++++++++++++++");
}
}
}//File: MyAnotherClass.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace ConsoleApplication2
{
public class MyAnotherClass
{
~MyAnotherClass()
{
Console.WriteLine("In MyAnotherClass destructor___________________________________");
}
}
}//File: Program.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
MyClass myClass = new MyClass();
MyAnotherClass myAnotherClass = new MyAnotherClass();
WeakReference myShortWeakReferenceObject = new WeakReference(myClass);
WeakReference myLongWeakReferenceObject = new WeakReference(myAnotherClass, true);
Console.WriteLine("Release managed resources by setting locals to null.");
myClass = null;
myAnotherClass = null;
Console.WriteLine("Check whether the objects are still alive.");
CheckStatus(myShortWeakReferenceObject, "myClass ", "myShortWeakReferenceObject");
CheckStatus(myLongWeakReferenceObject, "myAnotherClass", "myLongWeakReferenceObject");
Console.WriteLine("Programmatically cause GC.");
GC.Collect();
Console.WriteLine("Wait for GC runs the finalization methods.");
GC.WaitForPendingFinalizers();
//Check whether the objects are still alive.
CheckStatus(myShortWeakReferenceObject, "myClass ", "myShortWeakReferenceObject");
CheckStatus(myLongWeakReferenceObject, "myAnotherClass", "myLongWeakReferenceObject");
Console.WriteLine("Programmatically cause GC again. Let's see what will happen this time.");
GC.Collect();
//Check whether the objects are still alive.
CheckStatus(myShortWeakReferenceObject, "myClass ", "myShortWeakReferenceObject");
CheckStatus(myLongWeakReferenceObject, "myAnotherClass", "myLongWeakReferenceObject");
myAnotherClass = (MyAnotherClass)myLongWeakReferenceObject.Target;
Console.ReadLine();
}
static void CheckStatus(WeakReference weakObject, string strLocalVariableName, string strWeakObjectName)
{
Console.WriteLine(strLocalVariableName + (weakObject.IsAlive ? " is still alive." : " is not alive."));
Console.WriteLine(strWeakObjectName + (weakObject.Target != null ? ".Target is not null." : ".Target is null."));
Console.WriteLine();
}
}
}
请你们想想若是MyAnotherClass类没有Finalize方法呢?
或者:若是咱们注释掉这行: GC.WaitForPendingFinalizers();试着多执行此代码屡次, 看看每次会输出什么呢?是否是Finzalization方法被执行的时机不肯定?
弱引用是为大对象准备的。在实际当中,若是不用弱引用,只用强引用,则用过了该大对象,而后将强引用置null,让GC能够回收它,可是没过多久咱们又须要这个大对象了,可是已经没有办法找回原来的对象,只好从新建立实例,这样就浪费了建立实例所需的计算资源;而若是不置null,就会占用不少内存资源。对于这种状况,咱们能够建立一个这个大对象的弱引用,这样在内存不够时将强引用置null,让GC能够回收,而在没有被GC回收前,若是咱们短期内还须要该大对象,咱们还能够再次找回该对象,不用再从新建立实例。是否是节省了一些开销?
垃圾收集的通常流程
如下是垃圾收集的通常流程,受应用场景(如服务器应用,并发和非并发)影响,具体的垃圾回收流程可能有所不一样。
1. 挂起.net应用的全部线程
2. 找到可回收的对象
3. 回收可回收的对象并压缩托管堆
4. 继续.net应用的全部线程
垃圾收集的模式
工做站模式(workstation mode)
这种模式下GC假设机器上运行的其余应用程序对CPU资源要求不高,而且该模式可使用并发或非并发的模式运行。
并发模式是默认的工做站模式,该模式下垃圾回收器分配一个额外的后台线程在应用程序运行时并发回收对象。一个线程由于分配对象形成第0代超出预算时,垃圾回收器挂起全部线程,判断须要回收哪些代,若是须要回收第2代,就会增长第0代的大小。而后应用程序恢复执行。并发回收是为了给用户更好的交互体验,适合客户端应用程序,可是同时要注意并发回收对性能有损害,使用更多地内存。
禁用并发模式的配置为
<configuration>
<runtime>
<gcConcurrentenabled="false"/>
</runtime>
< /configuration>
服务器模式 (servermode)
这种模式下GC假设机器上没有运行其余应用程序,全部的CPU均可以用来进行垃圾回收操做。在这种状况下虚拟内存按照CPU数量划分区域分开对待,每一个CPU上都运行一个GC线程负责回收本身的区域。