NET垃圾回收机制.doc_第1页
NET垃圾回收机制.doc_第2页
NET垃圾回收机制.doc_第3页
NET垃圾回收机制.doc_第4页
NET垃圾回收机制.doc_第5页
已阅读5页,还剩14页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

在.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中,它们被称之为根。 有了开始点,垃圾收集器接下来怎么做呢? 创建一个图, 一个描述对象间引用关系的图. 垃圾收集器首先假定所有在托管堆里面的对象都是不可到达的(或者说没有被引用的,不再需要的), 然后从根上的那些变量开始, 针对每一个根上的变量,找出其引用的托管堆上的对象,将找到的对象加入这个图, 然后再沿着这个对象往下找,看看它有没有引用另外一个对象,有的话,继续将找到的对象加入图中,如果没有的话,就说明这条链已经找到尾部了。垃圾收集器就去从根上的另外一个变量开始找,直到根上的所有变量都找过了, 然后垃圾收集器才停止查找。值得一提的是,在查找过程中, 垃圾收集器有些小的优化,如: 由于对象间的引用关系可能是比较复杂的, 所以有可能找到一个对象, 而此对象已经加入图了, 那么垃圾收集器就不再在此条链上继续查找, 转去其他的链上继续找。这样对垃圾收集器的性能有所改善。 垃圾收集器建好这个图之后, 剩下那些没有在这个图中的对象就是不再需要的. 垃圾收集器就可以回收它们占用的空间。 内存释放和压缩 创建对象引用图之后,垃圾回收器将那些没有在这个图中的对象(即不再需要的对象)释放。释放内存之后, 出现了内存碎片, 垃圾回收器扫描托管堆,找到连续的内存块,然后移动未回收的对象到更低的地址, 以得到整块的内存,同时所有的对象引用都将被调整为指向对象新的存储位置。这就象一个夯实的动作。也就是说,一个对象即使没有被清除,由于内存压缩,导致他的引用位置发生变化。 下面要说到的是代的概念。代概念的引入是为了提高垃圾收集器的整体性能。代 请想一想如果垃圾收集器每次总是扫描所有托管堆中的对象,对性能会有什么影响。会不会很慢?是的。微软因此引入了代的概念。 为什么代的概念可以提高垃圾收集器的性能?因为微软是基于对大量编程实践的科学估计,做了一些假定而这些假定符合绝大多数的编程实践:越新的对象,其生命周期越短。 越老的对象,其生命周越长。 新对象之间通常有强的关系并被同时访问。 压缩一部分堆比压缩整个堆快。 有了代的概念,垃圾回收活动就可以大部分局限于一个较小的区域来进行。这样就对垃圾回收的性能有所提高。让我们来看垃圾收集器具体是怎么实现代的: 第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维护的结束队列中去。view plaincopy to clipboardprint?public class BaseObj public BaseObj() protected override void Finalize() / Perform resource cleanup code here. / Example: Close file/Close network connection Console.WriteLine(In Finalize.); 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方法已经被执行,就表明该对象没有重生。 按照上面的例子,如果执行如下代码会发生什么呢?view plaincopy to clipboardprint?/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. Lets 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(); /File: MyClass.csusing System;using System.Collections.Generic;using System.Text;namespace ConsoleApplication2 class MyClass MyClass() Console.WriteLine(In MyClass destructor+); /File: MyAnotherClass.csusing System;using System.Collections.Generic;using System.Text;namespace ConsoleApplication2 public class MyAnotherClass MyAnotherClass() Console.WriteLine(In MyAnotherClass destructor_); /File: Program.csusing 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. Lets 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应用的所有线程垃圾收集的模式CLR4.0之前,一共有三种模式的GC,分别针对不同类型的应用程序而优化:Server版非并发GC, Workstation版并发GC, 和Workstation版非并发GC. Server GC - Non ConcurrentServer GC是针对服务器应用而进行优化的,目的是为了保证其高吞吐量和高的规模可扩展性。Server GC 为每一个处理器分配一个托管堆和一个GC线程。各GC线程独立工作在各自的堆上,这样最大程度地减少了锁,从而保证了此种情况下的高效。这些GC线程是非并发的(第0代,第1代,第2代都是非并发的,即在收集时都要停止所有应用程序的线程,直到收集结束)。同时这些GC线程是专门的线程,不同于应用程序的线程。实际的Server GC工作流程是这样的:1. 应用程序的线程在其托管堆上分配空间2. 其托管堆没有更多空间来分配托管对象3. 于是触发一个事件,让GC线程来做收集,并等待GC线程完成收集4. GC线程运行,完成收集并发出完成事件(在GC线程运行期间,应用程序线程是暂停的)5. 应用程序的线程继续运行 这种类型的GC只有在多处理器的机器上可见,如果你在单处理器上的设置这种模式,那你将得到workstation版本非并发的GC。 A的应用在多cpu的机器上默认使用这种模式,还有其他一些类型的服务器应用也在多cpu的机器上使用这种模式。例如如果你想在Windows服务上使用server GC模式,你可以在应用程序配置文件中做如下设置: Workstation GC Concurrent Winform应用程序和Windows services 服务程序默认采用这种模式。 这种模式是针对交互式应用程序优化的,这种程序要求应用程序不能暂停,即使是一个相对很短暂的时间也是不行的。因为暂停进程会让用户界面闪烁或者当点击按钮的时候感觉应用程序没有响应。 在这种模式下,第0代和第1代的收集仍然是要暂停应用程序的线程直到收集完毕,因为第0代和第1代的收集速度很快,所以没有必要去做并行。所谓的并发是在需要第2代收集时,我们可以选择让第2代收集并行地执行或者暂停应用程序的所有线程直到收集结束。如果我们没有做选择,那么默认的就是第2代收集是并行的。 有一个专门的GC线程来并行地收集垃圾。在第2代收集时,用更多CPU周期和堆内存来换取更短的用户界面停顿时间。 并行收集仍然需要在收集过程中暂停应用程序多次,但这几次暂停相对较短,对UI线程影响较小。应用程序线程在并行收集进行中可以同时分配对象。这就要求第0代有较大的空间和较高的触发Gen 0收集的阀值。如果应用程序用完第0代的内存,而并行收集还没有进行完毕,那么应用程序不得不暂停下来,等待并行收集的完成。如果我们要做选择,让应用程序在并行模式下运行,可以在应用程序的config文件中写上: 注: Jeffrey Richter似乎在其书中说concurrent GC只有在多CPU机器上有。但是经过Mark的调查,实际上concurrent GC在单CPU上也有。 这种模式与Server GC类似,推荐为那种运行在单个cpu机器上服务类型的应用程序使用。与Server GC不同的是:其收集线程即等同于应用程序线程。这是这种模式的工作流程:1) 应用程序线程在堆上分配对象2) 第0代没有足够空间It3) 在同一线程上引发垃圾收集4) 垃圾收集器调用SuspendEE函数暂停应用程序线程5) GC做收集工作6) GC调用RestartEE来继续应用程序的线程GC 7) 应用程序的线程继续运行可以修改应用程序的配置来把 concurrency 关闭。 下面是三种模式的对比表: 以上我们说到了CLR 4.0以前垃圾收集的大部分方面。 掌握前面的知识有助于理解CLR 4.0所带来的变化。CLR 2.0以后GC

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论