资源描述
.NET.NET技术技术技术技术 1 of page第一部分第一部分 Microsoft.NET Microsoft.NET 框架基本原理框架基本原理第二部分第二部分 类型与通用语言运行时类型与通用语言运行时第三部分第三部分 类型设计类型设计第四部分第四部分 基本类型基本类型第五部分第五部分 类型管理类型管理.NET.NET技术技术技术技术 2 of page第五部分第五部分 类型管理类型管理异常自动内存管理.NET.NET技术技术技术技术 3 of page第18章 异常 .NET.NET技术技术技术技术 4 of page18.1 异常处理的概念异常处理的概念看以下代码:class Program static void Main(string args)int values=new int 10;for(int i=1;i=10;i+)Console.WriteLine(valuesi);Console.ReadKey();.NET.NET技术技术技术技术 5 of page18.1 异常处理的概念异常处理的概念看以下代码:class Program static void Main(string args)int values=new int 10;for(int i=1;i=10;i+)Console.WriteLine(valuesi);Console.ReadKey();C#中数组下标从0开始,因而此代码在运行时将会发生一个“数组访问越界”错误。.NET.NET技术技术技术技术 6 of page类似于这种在程序中隐藏的错误,被称为“异常异常(Exception)”,它表明程序执行期间出现了一个非法的运行状况,期望程序进行的某项操作没能完成。注意,异常是在程序运行时间出现的,不是在编译期间出现的,编译器在编译时发现的错误是语法错误,不能称之为异常。.NET Framework 提供了多个异常类,在编程中常用的有以下几个:.NET.NET技术技术技术技术 7 of page异常说明System.ArithmeticException在算术运算期间发生的异常的基类System.ArrayTypeMismatchException 当向一个数组中存储对象时,如果由于被存储的元素的实际类型与数组的实际类型不兼容而导致存储失败,就会引发此异常System.DivideByZeroException在试图用零作除数时引发System.IndexOutOfRangeException 在试图使用小于零或超出数组界限的下标索引数组时引发System.InvalidCastException 从基类型或接口到派生类型的显式转换在运行时失败,引发此异常System.NullReferenceException尝试使用未创建的对象,引发此异常System.OutOfMemoryException分配内存(通过new)失败时引发System.OverflowException在checked上下文中的算术运算溢出时引发System.StackOverflowException 当执行堆栈由于保存了太多挂起的方法调用而耗尽时,就会引发此异常System.TypeInitializationException 在静态构造函数引发异常并且没有可以捕捉到它的catch子句时引发.NET.NET技术技术技术技术 8 of page异常处理的目的是根据不同的异常情况提供不同的处置方法,使程序更稳定、更安全。异常处理的主要用途是提供准确的错误消息,解释失败的原因、位置和错误类型等,同时提供一定的恢复能力,尽可能地降低出错的机率。.NET.NET技术技术技术技术 9 of page使用try、catch 和 finally 处理异常的结构如下:处理异常的结构如下:try /可能引发异常的语句-try语句块(1)catch(Exception e)/对异常进行处理的语句-catch语句块(2)finally /“打扫战场”的语句-finally语句块(3)18.2 编程实现异常处理编程实现异常处理18.2.1 try、catch 和和 finally.NET.NET技术技术技术技术 10 of page异常处理机制将代码分成3大块。第(1)块是正常执行程序功能的语句块,其中有可能在运行时引发错误。第(2)块在程序正常运行时不会执行,仅当有异常出现时转到此块执行。第(3)块不管程序执行时有无异常出现都会执行。当程序正常运行时,程序的执行流程为:第(1)块 第(3)块 当第(1)块中有代码引发了一个异常时,程序的执行流程为:第(1)块 第(2)块 第(3)块可以有多个catch语句块,每个代码块捕获一种异常(由catch后的参数类型决定,称为“异常筛选器(Exception Filter)”)。.NET Framework中使用catch语句只能捕获Exception类及其子类的对象。当一个异常被引发后,如果应用程序没有提供合适的异常处理代码,应用程序进程将会被CLR强制中止。.NET.NET技术技术技术技术 11 of page异常处理机制将代码分成3大块。第(1)块是正常执行程序功能的语句块,其中有可能在运行时引发错误。第(2)块在程序正常运行时不会执行,仅当有异常出现时转到此块执行。第(3)块不管程序执行时有无异常出现都会执行。当程序正常运行时,程序的执行流程为:当程序正常运行时,程序的执行流程为:第(第(1)块)块 第(第(3)块)块 当第(当第(1)块中有代码引发了一个异常时,程序的执行流程为:)块中有代码引发了一个异常时,程序的执行流程为:第(第(1)块)块 第(第(2)块)块 第(第(3)块)块可以有多个catch语句块,每个代码块捕获一种异常(由catch后的参数类型决定,称为“异常筛选器(Exception Filter)”)。.NET Framework中使用catch语句只能捕获Exception类及其子类的对象。当一个异常被引发后,如果应用程序没有提供合适的异常处理代码,应用程序进程将会被CLR强制中止。.NET.NET技术技术技术技术 12 of page异常处理机制将代码分成3大块。第(1)块是正常执行程序功能的语句块,其中有可能在运行时引发错误。第(2)块在程序正常运行时不会执行,仅当有异常出现时转到此块执行。第(3)块不管程序执行时有无异常出现都会执行。当程序正常运行时,程序的执行流程为:第(1)块 第(3)块 当第(1)块中有代码引发了一个异常时,程序的执行流程为:第(1)块 第(2)块 第(3)块可以有多个可以有多个catch语句块,每个代码块捕获一种异常语句块,每个代码块捕获一种异常(由由catch后的参数后的参数类型决定,称为类型决定,称为“异常筛选器异常筛选器(Exception Filter)”)。.NET Framework中使用中使用catch语句只能捕获语句只能捕获Exception类及其子类的对象。类及其子类的对象。当一个异常被引发后,如果应用程序没有提供合适的异常处理代码,应用程序进程将会被CLR强制中止。.NET.NET技术技术技术技术 13 of page注意:注意:指定由逗号分开的catch参数表是语法错误,catch只能有一个参数,即一条catch语句只能捕获此参数限定的那种类型的异常。另外,在某个try块后有两个不同的catch块捕获两个相同类型的异常也是语法错误。finally语句块是可选的,主要用于解决资源泄露问题,它位于catch语句块之后,CLR保证它们一定执行。注意:注意:finally语句块中也可能发生异常,如果这种情况发生,先前的异常被放弃。程序中也可使用 throw 关键字主动地抛出一个异常:throw new Exception(“我的新异常对象”);.NET.NET技术技术技术技术 14 of page上节中引发“数组访问越界”错误的代码可以用异常处理机制重写如下:class Program static void Main(string args)int values=new int 10;try for(int i=1;i=10;i+)Console.WriteLine(valuesi);.NET.NET技术技术技术技术 15 of page catch(IndexOutOfRangeException e)Console.WriteLine(“在输出values数组值时发生数组越界错误”);Console.WriteLine(“异常种类:”+e.GetType().Name);Console.WriteLine(“系统给出的出错信息:”+e.Message);Console.WriteLine(“系统调用堆栈信息:”+e.StackTrace);Console.WriteLine(“引发此错误的方法:”+e.TargetSite);Console.ReadKey();.NET.NET技术技术技术技术 16 of page当程序运行时,异常提示信息如下:在输出values数组值时发生数组越界错误 异常种类:IndexOutOfRangeException 系统给出的出错信息:索引超出了数组界限 系统调用堆栈信息:在 OnlyTest.Program.Main(String args)位置 C:OnlyTestProgram.cs:行号 11 引发此错误的方法:Void Main(System.String).NET.NET技术技术技术技术 17 of page.NET Framework异常处理的核心是Exception类,它是所有可捕获异常类的基类,程序发生异常时,CLR 会创建一个相应种类的异常对象来表示该异常。Exception 对象 e 的3个重要属性。属属 性性用用 途途e.GetType().Name 了解异常的类型名,此名字可用于在下一步编程中有针对性地对此类型异常进行处理e.Message告诉用户发生了什么事e.StackTrace 确定错误发生的位置,如果有可用的调试信息,还可显示源文件名和程序行号.NET.NET技术技术技术技术 18 of page .NET Framework 实现的异常处理具有以下特点:p处理异常时不用考虑生成异常的语言或处理异常的语言。p异常处,理时不要求任何特定的语言语法,而是允许每种语言定义自己的语法。p允许跨进程甚至跨计算机边界引发异常。为达以上目的,CLR为每个正在运行的程序创建了一个异常信息表。在异常信息表中,程序中每个方法都有一个关联的异常处理信息数组。如果方法中有受到保护的语句块,则此方法相关联的异常处理信息数组中就记录了当异常发生时,CLR自动调用异常处理代码所需的相关信息。如果某方法中没有受保护块,则其对应的异常处理信息数组为空。提示:提示:被 trycatch 包围的语句块称为受保护块受保护块。18.2.2 CLR 18.2.2 CLR 结构化异常处理原理结构化异常处理原理.NET.NET技术技术技术技术 19 of page当某一方法中发生异常时,CLR 在此方法对应的异常处理信息数组中搜索,以确定是哪一个受保护块引发的异常,以及应该由哪个catch块处理。(1)如果找到以上信息,CLR创建一个Exception对象(或其子类对象)来描述该异常。然后,CLR执行处理该异常的catch语句块,如果有finally语句块,接着执行finally语句块。(2)如果在当前方法中没有找到相关信息,则CLR搜索当前方法的每一个调用方,在调用者的异常处理信息数组中搜索,直到最顶层的调用者。这个由底向上的搜索过程,其信息被记录在一个堆栈中,称为“异常堆栈异常堆栈”。(3)如果任何调用者都没有处理这种异常的代码,则CLR允许使用一个调试器来处理该异常。如果用户放弃调试,则CLR引发一个UnhandledException事件,而这时如果应用程序也没有编写响应UnhandledException事件的代码,则CLR会结束此进程。.NET.NET技术技术技术技术 20 of page public void SomeMethod()try /这里执行一些操作 catch (NullReferenceException e)/处理一个空引用异常 catch (InvalidCastException e)/处理一个无效转型异常 catch /在C#中,该筛选器会捕获任何异常 /处理所有异常 .NET.NET技术技术技术技术 21 of page首先创建一个自定义的异常类MyException。class MyException:Exception public MyException(String info):base(info)接着,编写代码实现以下的方法调用链,在SomeFunc()方法中引发一个Exception异常。Main()FuncInvoker()SomeFunc()具体代码如下:异常的传播过程异常的传播过程.NET.NET技术技术技术技术 22 of page static void Main(string args)FuncInvoker();static void FuncInvoker()SomeFunc();static void SomeFunc()throw new MyException(“主动引发的异常”);异常的传播过程异常的传播过程.NET.NET技术技术技术技术 23 of page可以在整个异常“传输链”中的任何一环“打断”整个异常传输,以避免进入调试阶段。修改 FuncInvoker 函数:static void FuncInvoker()try SomeFunc();catch(MyException e)Console.WriteLine(程序中出现了异常);Console.WriteLine(其信息为:+e.Message);.NET.NET技术技术技术技术 24 of page如果最底层的SomeFunc()函数引发的不是MyException异常,而是其他类型的异常,则FuncInvoker方法中的trycatch块又不管用了,还是会引发CLR报告错误。解决方法:可以在FuncInvoker方法中再增加一个catch语句块,专门处理此种类型的异常。.NET.NET技术技术技术技术 25 of page由于程序中可以引发的异常种类很多,很难一一写代码处理,最保险的方法是在最顶层方法中捕获Exception异常。static void Main(string args)try FuncInvoker();catch(Exception e)Console.WriteLine(e.Message);.NET.NET技术技术技术技术 26 of page.NET.NET技术技术技术技术 27 of page.NET.NET技术技术技术技术 28 of page第19章 自动内存管理(垃圾收集).NET.NET技术技术技术技术 29 of page19.1 19.1 垃圾收集平台基本原理解析垃圾收集平台基本原理解析 访问一个资源所需要的几个步骤:1.调用中间语言(IL)中的 newobj 指令,为表示某个特定资源的类型实例分配一定的内存空间。2.初始化上一步所得的内存,设置资源的初始状态,从而使其可以为程序所用。一个类型的实例构造器负责做这样的初始化工作。3.通过访问类型成员来使用资源,这根据需要会有一些反复。4.销毁资源状态,执行清理工作。5.释放内存。这一步由垃圾收集器全权负责。.NET.NET技术技术技术技术 30 of page以上模式却是导致许多编程错误的主要原因之一。释放无用的内存、试图访问已经被释放的内存,这两类bug发生的时间和次序都难以预料;这两类bug的直接后果是资源泄露(内存消耗)和对象损毁(状态不稳定)。正确无误的资源管理通常是一件比较困难和单调的工作,它们极大地分 散开发人员解决实际问题的注意力。垃圾收集垃圾收集(garbage collection)机 制能够简化这种容易遗漏的内存管理任务。大多数类型表示的资源并不需要任何特殊的清理操作。对于一个表示(或者说封装)着非托管(操作系统)资源的类型,在其对象被销毁时,就必须执行一些清理代码。.NET.NET技术技术技术技术 31 of pageCLR 要求所有的内存资源都从托管堆托管堆(managed heap)分配而得。当应用程序进程完成初始化后,CLR 将保留(reserve)一块连续的地址空间,这段空间最初并不对应任何的物理内存,该地址空间即为托管堆。托管堆上维护着一个指针,称为 NextObjPtr。该指针标识着下一个新建对象分配时在托管堆中所处的位置。刚开始时,NextObjPtr 被设为CLR 保留地址空间的基地址。内存分配和资源初始化问题内存分配和资源初始化问题.NET.NET技术技术技术技术 32 of page 中间语言(IL)指令 newobj 负责创建新的对象。在代码运行时,newobj指令将导致CLR执行以下几步操作:1.计算类型所有字段(以及其基类所有的字段)所需要的字节总数。2.在前面所得字节总数的基础上再加上对象额外的附加成员所需的字节数。每个对象包括两个附加字段:一个方法表指针和一个SyncBlockIndex。3.CLR检查保留区域中的空间是否满足分配新对象所需的字节数如需要则提交物理内存。如果满足,对象将被分配在NextObjPtr指针所指示的地方。接着,类型的实例构造器被调用,IL指令newobj返回为其分配的内存地址。就在newobj指令返回新对象的地址之前,NextObjPtr指针会越过新对象所处的内存区域,并指示出下一个新建对象在托管堆中的地址。.NET.NET技术技术技术技术 33 of page对比C语言运行时中的堆分配内存时的情况。普通堆中,如果连续地创建几个对象,很可能被分散在地址空间的各个角落。但在托管堆中,连续分配的对象可以保证它们在内存中也是连续的。A B CNextObjPtr包含3个对象的托管堆.NET.NET技术技术技术技术 34 of page托管堆在实现的简单性和速度方面要优于C语言运行时中的堆。假设于应用程序的地址空间和存储空间是无限的。托管堆必须应用某种机制来允许做这样的假设。这种机制就是垃圾收集器。垃圾收集器工作原理:当应用程序调用 new 操作符创建对象时,托管堆中可能没有足够的地址空间来分配该对象。托管堆通过将对象所需要的字节总数添加到NextObjPtr 指针表示的地址上来检测这种情况。如果得到的结果超出了托管堆的地址空间范围,那么托管堆将被认为已经充满,这时就需要执行垃圾收集。.NET.NET技术技术技术技术 35 of page每个应用程序都有一组根(root)。一个根是一个存储位置,其中包含着一个指向引用类型的内存指针。该指针或者指向一个托管堆中的对象,或者被设为null。所有全局引用类型变量或静态引用类型变量都被认为是根。一个线程堆栈上所有引用类型的本地变量或者参数变量也被认为是一个根。在一个方法内,指向引用类型对象的CPU寄存器也被认为是一个根。当JIT编译器编译一个方法的IL代码时,除了产生本地CPU代码外,JIT编译器还会创建一个内部的表。从逻辑上讲,该表中的每一个条目都标识着一个方法的本地CPU指令的字节偏移范围,以及该范围中一组包含根的内存地址。内部表结构如图:19.2 19.2 垃圾收集算法垃圾收集算法.NET.NET技术技术技术技术 36 of page0 x00000000 0 x00000020 this,arg1,arg2,ECX,EDX0 x00000021 0 x00000122 this,arg2,fs,EBX0 x00000123 0 x00000145 fs 起始字节偏移 结尾字节偏移 根 如果在0 x00000021和0 x00000122 之间的代码执行时开始了垃圾收集,那么垃圾收集器将知道参数this、参数arg2、本地变量fs以及寄存器EBX都是根,它们引用的托管堆中的对象将不会被认为是可收集的垃圾对象。除此之外,垃圾收集器还可以遍历线程的调用堆栈,通过检测其中每一个方法的内部表来确定所有调用方法中的根。最后,垃圾收集器使用其他一些手段来获得存储在全局引用类型变量和静态引用类型变量中保存的根。JITJIT编译器生成的表,展示了本地代码偏移和方法中根的映射关系编译器生成的表,展示了本地代码偏移和方法中根的映射关系.NET.NET技术技术技术技术 37 of page当垃圾收集器开始执行时,它首先假设托管堆中所有的对象都是可收集的垃圾。然后,垃圾收集器遍历所有的根,构造出一个包含所有可达对象的图。例:A B C D E F G H I J NextObjPtr 根全局变量静态变量本地变量CPU寄存器垃圾收集执行前的托管堆.NET.NET技术技术技术技术 38 of page上图展示了一个分配有几个对象的托管堆,其中对象A、C、D和F为应用程序的根所直接引用。所有这些对象都是可达对象图的一部分。当对象D被添加到该图中时,垃圾收集器注意到它还引用着对象H,于是对象H也被添加到该图中。垃圾收集器就这样以递归的方式来遍历应用程序中所有的可达对象。例:A B C D E F G H I J NextObjPtr 根全局变量静态变量本地变量CPU寄存器垃圾收集执行前的托管堆.NET.NET技术技术技术技术 39 of page垃圾收集器接着线性地遍历托管堆以寻找包含可收集垃圾对象的连续区块。如果找到了较大的连续区块,垃圾收集器将会把内存中的一些非垃圾对象移到这些连续区块中以压缩托管堆。搬移内存中的对象将使所有指向这些对象的指针变得无效,所以垃圾收集器必须修改应用程序的根以使它们指向这些对象更新后的位置。在托管堆中的内存被压缩之后,托管堆上的NextObjPtr 指针将被设为指向最后一个非垃圾对象之后。垃圾收集执行后的托管堆如下图:.NET.NET技术技术技术技术 40 of page A C D F H NextObjPtr 根全局变量静态变量本地变量CPU寄存器垃圾收集执行后的托管堆.NET.NET技术技术技术技术 41 of page 两点重要认识:首先,不必再自己实现代码来管理应用程序中对象的生存期。其次,前面描述的bug将不复存在。因为任何不可从应用程序的根中访问的对象都会在某个时刻被收集,所以应用程序不可能再发生内存泄漏的情况。另外,应用程序也不能再访问已经被释放的对象。因为如果对象可达,它将不可能被释放;而如果对象不可达,应用程序必将无法访问到它。.NET.NET技术技术技术技术 42 of page下面代码演示了垃圾收集器是怎样分配和管理对象的。下面代码演示了垃圾收集器是怎样分配和管理对象的。class App static void Main()ArrayList a=new ArrayList();for (Int32 x=0;x 10000;x+)a.Add(new Object();Console.WriteLine(a.count);Console.WriteLine(“End of method”);.NET.NET技术技术技术技术 43 of page任何封装了非托管资源的类型,例如:文件、网络链接、套接字、互斥体等,都必须支持一种称作终止化终止化(finalization)的操作。终止化操作允许一种资源在它所占用的内存被回收之前首先执行一些清理工作。要提供终止化操作,必须为类型实现一个名为 Finalize 的方法。当垃圾收集器判定一个对象为可收集的垃圾时,它便会调用该对象的Finalize方法(如果存在的话)。如果一个封装了非托管资源的类型没有定义Finalize方法,那么这些非托管资源将得不到关闭,从而会导致某种程度的资源泄漏(前提是没有显式关闭对象所封装的非托管资源)。直到进程结束,这些托管资源才会被操作系统回收。19.3 19.3 终止化操作终止化操作.NET.NET技术技术技术技术 44 of page public sealed class OSHandle private IntPtr handle;public OSHandle(IntPtr handle)this.handle=handle;protected override void Finalize()try CloseHandle(handle);finally base.Finalize();public IntPtr ToHandle()return handle;public static implicit operator IntPtr(OSHandle osHandle)return osHandle.ToHandle();private extern static Boolean CloseHandle(IntPtr handle);定义一个封装着非托管资源的类型定义一个封装着非托管资源的类型.NET.NET技术技术技术技术 45 of page public sealed class OSHandle private IntPtr handle;public OSHandle(IntPtr handle)this.handle=handle;/当垃圾收集执行时,下面的析构器当垃圾收集执行时,下面的析构器(Finalize)方法将被方法将被 /调用,它将关闭非托管资源句柄调用,它将关闭非托管资源句柄 OSHandle()ColseHandle(handle);public IntPtr ToHandle()return handle;public static implicit operator IntPtr(OSHandle osHandle)return osHandle.ToHandle();private extern static Boolean CloseHandle(IntPtr handle);C#C#为定义为定义FinalizeFinalize方法提供了特殊的语法:方法提供了特殊的语法:.NET.NET技术技术技术技术 46 of page终止化操作的内部机理:当应用程序创建一个新对象时,new 操作符会为对象从托管堆上分配内存。如果该对象的类型定义了Finalize方法,那么在该类型的实例构造器运行之前,指向该对象的一个指针将被放到一个称作终止终止化链表化链表的数据结构里面。终止化链表是一个由垃圾收集器控制的内部数据结构。链表上的每一个条目都引用着一个对象,这实际是在告诉垃圾收集器在回收这些对象的内存之前先要调用它们的Finalize方法。.NET.NET技术技术技术技术 47 of page一个包含几个对象的托管堆。有些是从应用程序的根可达的对象,有些不是。当对象C、E、F、I和J被创建时,系统会检测到这些对象的类型定义了Finalize方法,于是将指向这些对象的指针添加到终止化链表中。A B C D E F G H I J 根全局变量静态变量本地变量CPU寄存器终止化链表 C E F I J终止化可达队列.NET.NET技术技术技术技术 48 of page当垃圾收集开始时,对象B、E、G、H、I和J为垃圾对象。垃圾收集器然后扫描终止化链表以查找其中是否有指向这些对象的指针。当找到这样的指针时,它们会被从终止化链表中移除,并添加到终止化可达队列。终止化可达队列中出现的对象表示该对象的Finalize方法即将被调用。A C D E F I J 根全局变量静态变量本地变量CPU寄存器终止化链表 C F E I J终止化可达队列.NET.NET技术技术技术技术 49 of pageFinalize方法是.NET内部的一个释放内存资源的方法。这个方法不对外公开,由垃圾收集器自己调用。Finalize方法可以确保托管对象在释放内存的同时不会泄漏非托管资源。问题:不能确定该方法会在何时被调用,而且由于它并不是一个公有方法,所以也不能显式地调用它。.NET.NET技术技术技术技术 50 of page要提供显式释放或者关闭对象的能力,一个类型通常要实现一种被称为Dispose的模式。Dispose 模式定义了开发人员在实现类型的显式资源清理功能时所要遵循的一些约定。如果一个类型实现了Dispose 模式,使用该类型的开发人员将能够知道当对象不再被使用时如何显式地释放掉它所占用的资源。Dispose 调用方法:要释放的资源对象.Dispose调用Dispose方法释放对象所封装的非托管资源。但并不会释放对象在托管堆中占用的内存资源,释放对象内存的工作仍由垃圾收集器负责,而且释放的时间仍不确定。19.4 Dispose19.4 Dispose模式:强制对象清理资源模式:强制对象清理资源.NET.NET技术技术技术技术 51 of pageClose 方法 和 Dispose 一样,只不过有的对象没有提供Dispose方法,只提供了Close方法,而Close其实在对象的类中,依然是调用了一个私有私有的的DisposeDispose方法。方法。总结:.NET 中提供了三种模式来回收内存资源:Dispose模式,Finalize方法、Close方法。.NET.NET技术技术技术技术 52 of page垃圾收集会给应用程序带来不小的性能损伤,CLR 的垃圾收集器提供了一些特殊的优化设计来大幅度提高垃圾收集的性能。代龄代龄是旨在提高垃圾收集器性能的一种机制。一个基于代龄的垃圾收集器有以下几点假设:(1)对象越新,其生存期越短。(2)对象越老,其生存期越长。(3)对托管堆的一部分执行垃圾收集要比对整个托管堆执行垃圾收集速度更快。19.5 19.5 对象的代龄对象的代龄.NET.NET技术技术技术技术 53 of page在托管堆初始化时,其中不包括任何对象。这时添加到托管堆的对象被称为第 0 代对象。简单的说,第 0 代对象就是那些新构造的对象,垃圾收集器还没有对它们执行过任何检查。新启动的应用程序中托管堆情况:分配有5个对象,经一段时间后,对象C和E将变为不可达对象。当CLR初始化时,它会为第 0 代对象选择一个阙值容量,假定为256K。当分配新对象导致第 0 代对象超过了为其设定的阙值容量时,垃圾收集器就必须启动了。代龄的工作机制代龄的工作机制 A B C D E第0代一个刚经过初始化的托管堆,其中包含着一些对象。这时所有的对象都处于第 0 代。垃圾收集还没有执行过.NET.NET技术技术技术技术 54 of page假设从对象A到E总共占用了256KB,那么当对象F被分配时,垃圾收集器就会启动。垃圾收集器判定对象C和E为垃圾对象,会压缩对象D使其邻接于对象B。此次垃圾收集中存活下来的对象被认为是第 1 代对象。经过一轮垃圾收集检查,托管堆情况如下图:A B D第1代经过一次垃圾收集之后,第 0 代对象中的存活者被提升为第一代对象,第 0 代对象暂时空缺第0代.NET.NET技术技术技术技术 55 of page随着应用程序运行,又有新的对象被分配而成为第 0 代对象。对象F到K被分配,应用程序继续运行,对象B、H和J又成为不可达对象,它们的内存也要在某一点被回收。A B D F G H I J K第1代第 0 代中又分配了新的对象,第 1 代中某些对象已经成为垃圾对象第0代.NET.NET技术技术技术技术 56 of page假设应用程序试图分配对象L,这将再一次是第0代对象超过它的阙值容量,必须开始执行第二次垃圾收集。需判定收集哪些对象?CLR也会为第 1 代对象选择一个阙值容量,假设为 2MB。当第二次垃圾收集开始执行时,它会查看第 1 代对象占用了多少内存。由于第 1 代对象占用的内存远少于2MB,所以垃圾收集器只会去检查第 0 代对象。对应假设一新创建的对象的生存期比较短。所以第0代对象中成为垃圾对象的数量会比较多,因此对第0代对象执行垃圾收集将有可能回收比较多的内存,忽略掉第1代对象会提高垃圾收集的速度。忽略掉第1代对象更重要的是垃圾收集器不用再遍历整个托管堆中的所有对象了,这对垃圾收集器的性能提升具有重大的意义。.NET.NET技术技术技术技术 57 of page 第二次垃圾收集只对第0代的垃圾对象进行回收,第1代的垃圾对象不会被回收,第二此垃圾收集后,托管堆情况如图:A B D F G I K第1代经过两次垃圾收集之后,第0代对象中的存活者被提升为第1代(第1代内存总量有所增长),第0代暂时空缺第0代.NET.NET技术技术技术技术 58 of page应用程序继续运行,并分配对象L到对象O。过一段时间后,对象G、L和 M 又成为不可达对象。如图:A B D F G I K L M N O第1代经过两次垃圾收集之后,第0代对象中的存活者被提升为第1代(第1代内存总量有所增长),第0代暂时空缺第0代.NET.NET技术技术技术技术 59 of page假设分配对象P又导致了第0代对象超过它的阙值容量,于是垃圾收集又被启动。因为第1代中所有对象的内存总量仍小于2MB,所以垃圾收集器仍决定只收集第0代对象,而忽略第1代中的不可达对象。垃圾收集执行完后,托管堆情况如图:A B D F G I K N O第1代经过三次垃圾收集之后,第0代对象中的存活者被提升为第1代(第1代内存容量再次增长),第0代暂时空缺第0代.NET.NET技术技术技术技术 60 of page第1代对象在缓慢增长。现在假设第1代对象的增长导致其内存总量到达了2MB这一阙值容量。这时应用程序继续运行,并分配对象P到对象S,这使得第0代也达到它的阙值容量。这时托管堆情况如图:A B D F G I K N O P Q R S第1代新对象被分配成为第0代,第1代中有了更多的垃圾第0代.NET.NET技术技术技术技术 61 of page当应用程序试图分配对象T时,因第0代对象已经充满,所以必须执行垃圾收集。但这一次垃圾收集器会发现第1代对象的内存容量已经超过了2MB这一阙值,所以垃圾收集器会同时收集第0代和第1代中所有的垃圾对象。垃圾收集执行完毕后,托管堆情况如图:D F I N O Q S第2代经过四次垃圾收集之后,第1代中的存活者被提升为第2代,第0代中的存活者被提升为第1代,第0代暂时空缺第0代第1代.NET.NET技术技术技术技术 62 of page第2代j中的对象至少经过了两次垃圾收集的检查。在产生第2代之前系统可能已经执行了多次垃圾收集,但只有在第1代对象的内存总量达到它的阙值容量时,垃圾收集器才会检查第1代对象,而在这之前系统可能已经对第0代对象执行了好几次垃圾收集。总结CLR托管堆只支持 3 个代龄:第 0 代、第 1 代和第 2 代。当CLR初始化时,它会为这三代选择 3 个阙值容量,分别为:256KB、2MB、10MB。选择阙值容量是为了提高系统性能,阙值容量越大,垃圾收集执行的频率就越低。性能提升源于初始假设:新对象存活时间比较短,而老对象倾向于继续存活。.NET.NET技术技术技术技术 63 of page垃圾收集器是一个自调节的垃圾收集器。这意味着垃圾收集器会在执行垃圾收集的过程中学习应用程序的行为。.NET.NET技术技术技术技术 64 of page
展开阅读全文